Commit a9c290ac authored by Korko's avatar Korko
Browse files

Use VueJS instead of handmade JS

parent cac3019c
......@@ -43,7 +43,7 @@ class RandomFormController extends Controller
'name' => $names[$i],
'email' => $emails[$i],
'phone' => $phones[$i],
'partner' => !empty($partners[$i]) ? $names[$partners[$i]] : null,
'partner' => (!empty($partners[$i]) && $partners[$i] !== -1) ? $names[$partners[$i]] : null,
];
}
......
......@@ -4,10 +4,27 @@
"keywords": ["framework", "laravel"],
"license": "MIT",
"type": "project",
"repositories": [
{
"type": "package",
"package": {
"name": "korko/laravel5-recaptcha2",
"version": "dev-Korko-patch-1",
"source": {
"url": "https://github.com/Korko/laravel5-recaptcha2.git",
"type": "git",
"reference": "Korko-patch-1"
},
"autoload": {
"classmap": [""]
}
}
}
],
"require": {
"php": ">=5.5.9",
"laravel/framework": "5.1.*",
"crazyinventor/laravel5-recaptcha2": "^1.0",
"korko/laravel5-recaptcha2": "dev-Korko-patch-1",
"league/statsd": "dev-master",
"korko/validator": "dev-master",
"aloha/twilio": "^3.0"
......
......@@ -4,8 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "3f158e295e8f5a445f3309abb5f53c03",
"content-hash": "73ee6e2eccd5123cf40fa349e8ef112c",
"hash": "2d68d1c15c0bad5443ea16cc35500a43",
"content-hash": "5d2a44695a303cd5abb4d98f59c335e7",
"packages": [
{
"name": "aloha/twilio",
......@@ -115,44 +115,6 @@
],
"time": "2015-11-09 22:51:51"
},
{
"name": "crazyinventor/laravel5-recaptcha2",
"version": "1.3",
"source": {
"type": "git",
"url": "https://github.com/crazyinventor/laravel5-recaptcha2.git",
"reference": "a796d51e64ebe6f62e649d1ff2e91346b63f4c4c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/crazyinventor/laravel5-recaptcha2/zipball/a796d51e64ebe6f62e649d1ff2e91346b63f4c4c",
"reference": "a796d51e64ebe6f62e649d1ff2e91346b63f4c4c",
"shasum": ""
},
"require": {
"php": ">=5.4.0"
},
"type": "library",
"autoload": {
"psr-4": {
"CrazyInventor\\Lacaptcha\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "David Schneider",
"email": "david@crazyinventor.net",
"homepage": "http://www.crazyinventor.net",
"role": "Developer"
}
],
"description": "A package to use Google Recaptcha V2 with Laravel 5",
"time": "2016-09-09 10:43:06"
},
{
"name": "danielstjules/stringy",
"version": "1.10.0",
......@@ -454,6 +416,22 @@
],
"time": "2015-12-05 17:17:57"
},
{
"name": "korko/laravel5-recaptcha2",
"version": "dev-Korko-patch-1",
"source": {
"type": "git",
"url": "https://github.com/Korko/laravel5-recaptcha2.git",
"reference": "Korko-patch-1"
},
"type": "library",
"autoload": {
"classmap": [
""
]
},
"time": "2016-11-10 15:03:39"
},
{
"name": "korko/validator",
"version": "dev-master",
......@@ -3554,6 +3532,7 @@
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"korko/laravel5-recaptcha2": 20,
"league/statsd": 20,
"korko/validator": 20
},
......
......@@ -255,9 +255,6 @@ img {
.participant .participant-remove-wrapper {
width:179px;
}
.participant:nth-child(-n+2) .participant-remove {
display:none;
}
#mailContent {
max-width: 100%;
}
......
var app = new Vue({
el: '#form',
data: {
sending: false,
sent: false,
participants: [],
fieldErrors: {}
},
computed: {
emailUsed: function() {
var used = false;
for(i in this.participants) {
used = used || (this.participants[i].email !== '');
}
return used;
},
phoneUsed: function() {
var used = false;
for(i in this.participants) {
used = used || (this.participants[i].phone !== '');
}
return used;
},
errors: function() {
var errors = [];
for(field in this.fieldErrors) {
errors = errors.concat(this.fieldErrors[field]);
}
return errors;
}
},
created: function() {
this.addParticipant();
this.addParticipant();
},
methods: {
addParticipant: function() {
this.participants.push({
name: '',
email: '',
phone: '',
partner: -1
});
},
removeParticipant: function(idx) {
this.participants.splice(idx, 1);
},
submit: function(event) {
var postData = $(event.target).serializeArray();
var formURL = $(event.target).attr("action");
if(!this.sending && !this.sent) {
this.sending = true;
var app = this;
$.ajax({
url : formURL,
type: "POST",
data : postData,
success: function(data, textStatus, jqXHR) {
alertify.alert(jqXHR.responseJSON[0]);
app.sent = true;
},
error: function(jqXHR, textStatus, errorThrown) {
app.fieldErrors = jqXHR.responseJSON;
app.sending = false;
}
});
}
}
},
watch: {
sending: function(newVal) {
// If we reset the sending status, reset the captcha
if(!newVal) {
grecaptcha.reset();
}
},
sent: function(newVal) {
// If sent is a success, scroll to the message
if(newVal) {
$.scrollTo($('#success-wrapper'), 800, {offset: -120});
}
},
errors: function(newVal) {
// If there's new errors, scroll to them
if(newVal.length) {
$.scrollTo($('#errors-wrapper'), 800, {offset: -120});
}
}
}
})
$('.participant-add').on('click', function() {
var el = newParticipant(0);
document.getElementById("participants").getElementsByTagName("tbody")[0].appendChild(el);
});
$(document.body).on('click', '.participant-remove', function(event) {
var participant = $(event.target).parents('.participant')[0];
var index = Array.prototype.slice.call(document.getElementsByClassName("participant")).indexOf(participant);
participant.parentNode.removeChild(participant);
var parts = document.getElementsByClassName("participant"), select;
for (var i = 0; i < parts.length; i++) {
select = parts[i].getElementsByClassName("participant-partner")[0];
if (parseInt(select.options[select.selectedIndex].value, 10) === index) {
select.selectedIndex = 0;
}
for (var j = 0; j < select.options.length; j++) {
if (parseInt(select.options[j].value, 10) === index) {
select.removeChild(select.options[j]);
break;
}
}
}
});
$(document.body).on('blur', '.participant-name', function(event) {
var participant = $(event.target).parents('.participant')[0];
// Update for all participants the "participant-partner" so that the name is corrected or added
var index = Array.prototype.slice.call(document.getElementsByClassName("participant")).indexOf(participant);
var parts = document.getElementsByClassName("participant"), select, option, founded;
for (var i = 0; i < parts.length; i++) {
if (i === index) continue;
select = parts[i].getElementsByClassName("participant-partner")[0];
founded = false;
for (var j = 0; j < select.options.length; j++) {
if (parseInt(select.options[j].value, 10) === index) {
select.options[j].text = (j+1)+'. '+participant.getElementsByClassName("participant-name")[0].value;
founded = true;
}
}
if (!founded) {
option = document.createElement("option");
option.value = index;
option.text = (index+1)+'. '+participant.getElementsByClassName("participant-name")[0].value;
select.appendChild(option);
}
}
});
$('#form-mail-group').prop('disabled', 'disabled');
$(document.body).on('blur', '.participant-email', function(event) {
$('#form-mail-group').prop('disabled', 'disabled');
$('.participant-email').each(function() {
if($(this).val()){
$('#form-mail-group').prop('disabled', false);
return false;
}
});
});
$('#form-sms-group').prop('disabled', 'disabled');
$(document.body).on('blur', '.participant-phone', function(event) {
$('#form-sms-group').prop('disabled', 'disabled');
$('.participant-phone').each(function() {
if($(this).val()){
$('#form-sms-group').prop('disabled', false);
return false;
}
});
});
// Validate a phone number field
var locked = false;
$("#form form").submit(function(e) {
var postData = $(this).serializeArray();
var formURL = $(this).attr("action");
if(!locked) {
locked = true;
$.ajax({
url : formURL,
type: "POST",
data : postData,
success:function(data, textStatus, jqXHR) {
alertify.alert(jqXHR.responseJSON[0]);
$('#form form fieldset').prop('disabled', 'disabled');
$('#errors-wrapper').hide();
$('#success-wrapper').show();
$.scrollTo($('#success-wrapper'), 800, {offset: -120});
},
error: function(jqXHR, textStatus, errorThrown) {
grecaptcha.reset();
$('#errors').html('');
$(jqXHR.responseJSON).each(function(field, errors) {
$.each(errors, function(k, err) {
$('#errors').append('<li>'+err+'</li>');
});
});
$('#errors-wrapper').show();
$.scrollTo($('#errors-wrapper'), 800, {offset: -120});
locked = false;
}
});
}
e.preventDefault(); //STOP default action
return false;
});
function newParticipant() {
// Clone the first participant
var el = document.getElementsByClassName("participant")[0].cloneNode(true);
// Reset inputs
var inputs = el.getElementsByTagName("input");
for (i in inputs) {
inputs[i].value = "";
}
// Get the max counter and refill the select "participant-partner" with correct values
var max = 0;
var select = el.getElementsByClassName("participant-partner")[0];
var parts = document.getElementsByClassName("participant");
for (var i = 0; i < parts.length; i++) {
max = Math.max(max, parts[i].getElementsByClassName('counter')[0].innerHTML);
if (parts[i].getElementsByClassName("participant-name")[0].value === '') continue;
founded = false;
for (var j = 0; j < select.options.length; j++) {
if (parseInt(select.options[j].value, 10) === i) {
select.options[j].text = (j+1)+'. '+parts[i].getElementsByClassName("participant-name")[0].value;
founded = true;
}
}
if (!founded) {
option = document.createElement("option");
option.value = i;
option.text = (i+1)+'. '+parts[i].getElementsByClassName("participant-name")[0].value;
select.appendChild(option);
}
}
// Sort the "participant-partner" select so that values are well ordered
var options = $(select.options);
options.sort(function(a,b) {
if (a.value > b.value) return 1;
else if (a.value < b.value) return -1;
else return 0;
});
$(select).empty().append(options);
// Set the counter
el.getElementsByClassName('counter')[0].innerHTML = max + 1;
return el;
}
This diff is collapsed.
......@@ -59,6 +59,8 @@ The key word \"{TARGET}\" is mandatory in the message body to give each person h
'mail.content.tip1' => "Use \"{SANTA}\" for the receiver of the email and \"{TARGET}\" for the name of the target.",
'mail.content.tip2' => "Tip: Remember to remind the date, location and the amount of the gift.",
'mail-sms' => "and/or",
'sms.content' => "Sms content <span class=\"tip\">(max 130 chars)</span>",
'sms.content.placeholder' => "e.g. Hi {SANTA}, for the secret santa party, your target is {TARGET}. As a reminder, the amount of the gift is £3!",
'sms.content.tip1' => "Use \"{SANTA}\" for the receiver of the sms and \"{TARGET}\" for the name of the target.",
......
......@@ -59,6 +59,8 @@ Le mot clef \"{TARGET}\" est obligatoire dans le corps du message afin de donner
'mail.content.tip1' => "Utilisez \"{SANTA}\" pour le nom de celui qui recevra le mail et \"{TARGET}\" pour le nom de sa cible.",
'mail.content.tip2' => "Conseil : Pensez à rappeler la date, le lieu ainsi que le montant du cadeau.",
'mail-sms' => "et/ou",
'sms.content' => "Contenu du sms <span class=\"tip\">(130 caractères max)</span>",
'sms.content.placeholder' => "ex : Salut {SANTA}, pour la soirée secret santa du 23 décembre chez Martin, ta cible c'est {TARGET}. Pour rappel, le montant du cadeau est de 3€ !",
'sms.content.tip1' => "Utilisez \"{SANTA}\" pour le nom de celui qui recevra le sms et \"{TARGET}\" pour le nom de sa cible.",
......
......@@ -53,7 +53,7 @@
<!-- js -->
<script src="media/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js"></script>
</head>
<body data-spy="scroll" data-target="#navbar" data-offset="120" >
<body data-spy="scroll" data-target="#navbar" data-offset="120">
<div id="menu" class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<nav id="navbar">
......@@ -190,17 +190,18 @@
<h2 class="section-title text-center">@lang('form.section.go.title')</h2>
<p class="lead main text-center">@lang('form.section.go.subtitle')</p>
<div class="row text-center form">
<div id="success-wrapper" class="alert alert-success" style="display: none">
<div id="success-wrapper" class="alert alert-success" v-show="sent">
@lang('form.success')
</div>
<div id="errors-wrapper" class="alert alert-danger" style="display: none">
<div id="errors-wrapper" class="alert alert-danger" v-show="errors.length && !sent">
<ul id="errors">
<li v-for="error in errors">@{{ error }}</li>
</ul>
</div>
<form action="{{ url('/') }}" method="post" autocomplete="off">
<fieldset>
<form action="{{ url('/') }}" @submit.prevent="submit" method="post" autocomplete="off">
<fieldset :disabled="sending || sent">
<legend>@lang('form.participants')</legend>
<div class="table-responsive form-group">
<table id="participants" class="table table-hover table-numbered">
......@@ -216,62 +217,57 @@
</thead>
<tbody>
{{-- Default is two empty rows to have two entries at any time --}}
@foreach(old('name', ['', '']) as $idx => $name)
<tr class="participant">
<tr class="participant" v-for="(participant, idx) in participants">
<td class="row">
<label>
<div class="input-group">
<span class="input-group-addon counter">{{ $idx+1 }}</span>
<input type="text" name="name[]" required="required" placeholder="@lang('form.name.placeholder')" value="{{ $name }}" class="form-control participant-name" />
<span class="input-group-addon counter">@{{ idx+1 }}</span>
<input type="text" name="name[]" required="required" placeholder="@lang('form.name.placeholder')" v-model="participant.name" class="form-control participant-name" />
</div>
</label>
</td>
<td class="row border-left">
<input type="email" name="email[]" placeholder="@lang('form.email.placeholder')" value="{{ array_get(old('email', []), $idx) }}" class="form-control participant-email" />
<input type="email" name="email[]" placeholder="@lang('form.email.placeholder')" v-model="participant.email" class="form-control participant-email" :required="!participant.phone" />
</td>
<td>
et/ou
@lang('form.mail-sms')
</td>
<td class="row border-right">
<input type='tel' pattern='0[67]\d{8}' maxlength="10" name="phone[]" placeholder="@lang('form.phone.placeholder')" value="{{ array_get(old('phone', []), $idx) }}" class="form-control participant-phone" />
<input type='tel' pattern='0[67]\d{8}' maxlength="10" name="phone[]" placeholder="@lang('form.phone.placeholder')" v-model="participant.phone" class="form-control participant-phone" :required="!participant.email" />
</td>
<td class="row">
<select name="partner[]" class="form-control participant-partner">
<option value="" {{ !array_get(old('partner'), $idx) ? 'selected="selected"' : '' }}>@lang('form.partner.none')</option>
@foreach(array_diff_key(old('name', []), [$idx => false]) as $idx2 => $name)
{{ $selected = (array_get(old('partner'), $idx) !== '' && intval(array_get(old('partner'), $idx)) === $idx2) }}
<option value="{{ $idx2 }}" {{ $selected ? 'selected="selected"' : '' }}>{{ ($idx2+1).". ".$name }}</option>
@endforeach
<option value="" :selected="participant.partner === -1">@lang('form.partner.none')</option>
<option v-for="(partner, pidx) in participants" v-if="partner.name && idx !== pidx" :value="pidx" :selected="participant.partner === pidx">@{{ partner.name }}</option>
</select>
</td>
<td class="row participant-remove-wrapper">
<button type="button" class="btn btn-danger participant-remove"><span class="glyphicon glyphicon-minus"></span><span> @lang('form.partner.remove')</span></button>
<button type="button" class="btn btn-danger participant-remove" :disabled="idx < 2" @click="removeParticipant(idx)"><span class="glyphicon glyphicon-minus"></span><span> @lang('form.partner.remove')</span></button>
</td>
</tr>
@endforeach
</tbody>
</table>
<button type="button" class="btn btn-success participant-add"><span class="glyphicon glyphicon-plus"></span> @lang('form.partner.add')</button>
<button type="button" class="btn btn-success participant-add" @click="addParticipant()"><span class="glyphicon glyphicon-plus"></span> @lang('form.partner.add')</button>
</div>
<div class="row" id="contact">
<fieldset id="form-mail-group" class="col-md-6">
<fieldset id="form-mail-group" class="col-md-6" :disabled="!this.emailUsed">
<div class="form-group">
<label for="mailTitle">@lang('form.mail.title')</label>
<input id="mailTitle" type="text" name="title" required="required" placeholder="@lang('form.mail.title.placeholder')" value="{{ old('title') }}" class="form-control" />
<input id="mailTitle" type="text" name="title" :required="this.emailUsed" placeholder="@lang('form.mail.title.placeholder')" value="{{ old('title') }}" class="form-control" />
</div>
<div class="form-group">
<label for="mailContent">@lang('form.mail.content')</label>
<textarea id="mailContent" name="contentMail" required="required" placeholder="@lang('form.mail.content.placeholder')" class="form-control" rows="3">{{ old('contentMail') }}</textarea>
<textarea id="mailContent" name="contentMail" :required="this.emailUsed" placeholder="@lang('form.mail.content.placeholder')" class="form-control" rows="3">{{ old('contentMail') }}</textarea>
<p class="help-block">@lang('form.mail.content.tip1')</p>
<p class="help-block">@lang('form.mail.content.tip2')</p>
</div>
</fieldset>
<fieldset id="form-sms-group" class="col-md-6">
<fieldset id="form-sms-group" class="col-md-6" :disabled="!this.phoneUsed">
<div class="form-group">
<label for="smsContent">@lang('form.sms.content')</label>
<textarea id="smsContent" name="contentSMS" required="required" placeholder="@lang('form.sms.content.placeholder')" class="form-control" rows="3" maxlength="130">{{ old('contentSMS') }}</textarea>
<textarea id="smsContent" name="contentSMS" :required="this.phoneUsed" placeholder="@lang('form.sms.content.placeholder')" class="form-control" rows="3" maxlength="130">{{ old('contentSMS') }}</textarea>
<p class="help-block">@lang('form.sms.content.tip1')</p>
<p class="help-block">@lang('form.sms.content.tip2')</p>
</div>
......@@ -279,7 +275,7 @@
</div>
<div class="form-group btn">
{!! Recaptcha::render() !!}
{!! Recaptcha::renderElement(['data-theme' => 'light']) !!}
</div>
{{ csrf_field() }}
......@@ -314,10 +310,13 @@
<script type="text/javascript" src="media/js/jquery.actual.min.js"></script>
<script type="text/javascript" src="media/js/jquery.scrollTo.min.js"></script>
<script type="text/javascript" src="media/js/bootstrap.min.js"></script>
<script type="text/javascript" src="media/js/vue.min.js"></script>
<script type="text/javascript" src="media/js/app.js"></script>
<script type="text/javascript" src="media/js/main.js"></script>
<script type="text/javascript" src="media/js/randomForm.js"></script>
<script type="text/javascript" src="media/js/alertify.min.js"></script>
{!! Recaptcha::renderScript(App::getLocale()) !!}
@if(Session::has('message'))
<script type="text/javascript">
alertify.alert("{{ session('message') }}");
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment