Tips: Add interactivity (#4155)

* initial commit.

* Fixed NaN issue with bill amount.

* Finish up tips.

* Fixed markup.

* Remove mathjs as dep

* Finishing touches...

* Tweak design.

* Add class

* Add simplified css

* overhaul ui

* Add total per person.

* Add secondary text

* Update input class.

* Add further bindings.

* Removed rouge file.
master
PJ Hampton 2017-06-23 22:12:52 +01:00 committed by Zaahir Moolla
parent 9edc2f66bc
commit ee9d459966
5 changed files with 301 additions and 69 deletions

View File

@ -1,12 +1,14 @@
package DDG::Goodie::Tips;
# ABSTRACT: calculate a tip/tax on a bill or a general percentage
# ABSTRACT: Calculates a tip on a bill or a general percentage
use strict;
use DDG::Goodie;
with 'DDG::GoodieRole::NumberStyler';
# Yes, 'of' is very generic, the guard should kick back false positives very quickly.
triggers any => 'tip', 'tips', 'of', 'tax';
my @generic_trigs = ('tip calculator', 'calculate tip', 'tips calculator', 'calculate tips', 'bill tip', 'tip cost');
triggers any => @generic_trigs;
triggers any => 'tip', 'tips', 'of';
zci answer_type => 'tip';
zci is_cached => 1;
@ -14,47 +16,54 @@ zci is_cached => 1;
my $number_re = number_style_regex();
handle query_lc => sub {
return unless (/^(?<p>$number_re)(?: ?%| percent) (?:(?<do_tip>(?<tax_or_tip>tip|tax) (?:on|for|of))|of)(?: an?)? (?<sign>[\$\-]?)(?<num>$number_re)(?: bill)?$/);
my ($p, $num, $sign) = ($+{'p'}, $+{'num'}, $+{'sign'});
my $style = number_style_for($p, $num);
$p = $style->for_computation($p) / 100;
$num = $style->for_computation($num);
my $t = $p * $num;
if ($+{'do_tip'}) {
my $subtotal = $style->for_display(sprintf "%.2f", $num);
my $tax_or_tip = ucfirst($+{'tax_or_tip'});
my $tax_or_tip_value = $style->for_display(sprintf "%.2f", $t);
my $total = $style->for_display(sprintf "%.2f", $num + $t);
my $tax_or_tip_answer = "Subtotal: \$$subtotal; $tax_or_tip: \$$tax_or_tip_value; Total: \$$total";
return $tax_or_tip_answer,
structured_answer => {
data => {
title => "$tax_or_tip_answer",
},
templates => {
group => 'text'
}
};
}
$t = sprintf "%.2f", $t if ($sign eq '$'); # Maybe this makes cents.
my $calculated_answer = $style->for_display($t);
my $percentage = $style->for_display($p * 100);
my $number = $style->for_display($num);
my $percent_answer = "$sign$calculated_answer is $percentage% of $sign$number";
return $percent_answer,
structured_answer => {
# sets up the vanilla UI with default values
# no values should be pased to the front-end
if($_ ~~ @generic_trigs) {
return '', structured_answer => {
data => {
title => "$percent_answer"
title => "Tip Calculator",
percentage => '',
bill => '',
},
templates => {
group => 'text'
group => 'text',
options => {
content => 'DDH.tips.content'
},
}
};
}
# The following code handles more verbose and
# complicated queries such as:
#
# - 20% tip on $400 bill
# - 14% tip for a $10 bill
return unless (/^(?<p>$number_re)(?: ?%| percent) (?:(?<do_tip>(?<tax_or_tip>tip) (?:on|for|of))|of)(?: an?)? (?<sign>[\$\-]?)(?<num>$number_re)(?: bill)?$/);
my ($p, $num) = ($+{'p'}, $+{'num'});
my $style = number_style_for($p, $num);
$p = $style->for_computation($p);
$num = $style->for_computation($num);
if ($+{'do_tip'}) {
return '', structured_answer => {
data => {
title => "Tip Calculator",
percentage => "$p",
bill => "$num",
},
templates => {
group => 'text',
options => {
content => 'DDH.tips.content'
},
},
};
} else {
return;
}
};
1;
1;

View File

@ -0,0 +1,33 @@
<div class="tips__col tips__col--left forty">
<div class="tips_input--section">
<label for="bill">Bill</label>
<input class="frm__input bg-clr--white" type="number" id="bill_input" min="0" step="0.01">
</div>
<div class="tips_input--section">
<label for="tip">Tip %</label>
<input class="frm__input bg-clr--white" type="number" id="bill_tip" min="0">
</div>
<div class="tips_input--section">
<label for="number of people">Number of People</label>
<input class="frm__input bg-clr--white" type="number" id="bill_people" value="1" min="0">
</div>
</div><div class="tips__col tips__col--right fifty">
<div class="tips__block tips__block--tip">
<div class="tips__label">
<h4 id="tip_label" class="text--primary">Tip</h4>
<div class="tips__pp hide text--seccondary t-s">Per Person</div>
</div><div id="tip" class="tips__amt text--primary">-</div>
</div>
<div class="tips__block tips__block--total">
<div class="tips__label">
<h4 id="total_label" class="text--primary">Total</h4>
<div class="tips__pp hide text--seccondary t-s">Per Person</div>
</div><div id="total" class="tips__amt text--primary t-bold">-</div>
</div>
</div>

View File

@ -0,0 +1,92 @@
.zci--tips label {
font-weight: bold;
font-size: 1.3em;
}
.zci--tips input {
display: block;
width: 100%;
margin-bottom: 10px;
font-size: 2em;
line-height: 1.5em;
font-weight: bold;
text-align: center;
}
.zci--tips .tips__col {
display: inline-block;
}
.zci--tips .tips__col--left {
border-right: 1px solid #e0e0e0;
padding-right: 2em;
margin-right: 2em;
}
.zci--tips .tips__block {
margin-bottom: 1em;
}
.zci--tips .tips__label {
display: inline-block;
}
.zci--tips .tips__label h4 {
padding-bottom: 0px;
}
.zci--tips .tips__label h4 + div {
font-size: 1em;
}
.zci--tips .tips__block .tips__amt {
font-size: 3em;
}
/* Mobile */
.is-mobile .zci--tips .tips__col--left {
border-right: none;
padding-right: 0;
}
.is-mobile .zci--tips .tips__col--right {
width: 100%;
border-top: 1px solid #e0e0e0;
margin-top: 1em;
padding-top: 1em;
padding-left: 0;
}
.is-mobile .zci--tips input {
font-size: 1.6em;
line-height: 1.7em;
width: 100%;
}
.is-mobile .zci--tips .tips__block {
margin-bottom: 0;
}
.is-mobile .zci--tips .tips__block .tips__label {
width: 30%;
}
.is-mobile .zci--tips .tips__block .tips__label h4 {
font-size: 3em;
padding-top: 0;
}
.is-mobile .zci--tips .tips__block .tips__label h4.tips__label--pp {
font-size: 1.3em;
}
.is-mobile .zci--tips .tips__block {
display: inline-block;
width: 100%
}
.is-mobile .zci--tips .tips__amt {
display: inline-block;
width: 70%;
text-align: right;
}

96
share/goodie/tips/tips.js Normal file
View File

@ -0,0 +1,96 @@
DDH.tips = DDH.tips || {};
(function(DDH) {
"use strict";
var initialized = false;
var $dom, $inputs, $bill_input, $bill_tip, $bill_people, $tip_label, $total_label, $tip, $total, $tips_pp, $tips_labels;
/*
* setUpSelectors
*
* Sets up the jQuery selectors when the IA is built
*/
function setUpSelectors() {
$dom = $(".zci--tips");
// the inputs
$inputs = $dom.find('input');
$bill_input = $dom.find("#bill_input");
$bill_tip = $dom.find("#bill_tip");
$bill_people = $dom.find("#bill_people");
// the display labels
$tip_label = $dom.find("#tip_label");
$total_label = $dom.find("#total_label");
$tip = $dom.find("#tip");
$total = $dom.find("#total");
$tips_pp = $dom.find(".tips__pp");
$tips_labels = $dom.find(".tips__label h4");
}
/**
* calculateTip
*
* Calculates the tip and sets the display
*/
function calculateTip() {
var bill_input = $bill_input.val();
var bill_tip = $bill_tip.val();
var bill_people = $bill_people.val();
if(bill_input === "") {
bill_input = 0;
}
var tip = bill_input * (bill_tip / 100);
var tip_pp = tip / parseInt(bill_people);
var total = parseFloat(bill_input) + tip;
var total_pp = total / parseInt(bill_people);
if(bill_people > 1) {
$tips_pp.removeClass('hide');
$tips_labels.addClass('tips__label--pp');
$tip.text(tip_pp.toFixed(2));
$total.text(total_pp.toFixed(2));
} else {
$tip_label.text("Tip");
$tips_pp.addClass('hide');
$tips_labels.removeClass('tips__label--pp');
$total_label.text("Total");
$tip.text(tip.toFixed(2));
$total.text(total.toFixed(2));
}
}
DDH.tips.build = function(ops) {
// seed the tip calculator with some values
var init_bill = ops.data.bill || "100";
var init_percentage = ops.data.percentage || "20";
return {
onShow: function() {
if(!initialized) {
setUpSelectors();
$bill_input.val(init_bill);
$bill_tip.val(init_percentage);
calculateTip()
}
/**
* Event handlers to update the values when
* keys are pressed
*/
$inputs.bind('keyup click change mousewheel', function(_e) {
calculateTip()
});
initialized = true;
}
};
};
})(DDH);

View File

@ -10,46 +10,48 @@ zci answer_type => 'tip';
zci is_cached => 1;
sub make_structured_answer {
my ($type, $subtotal, $additive, $total) = @_;
my $title;
if ($type eq 'percentage') {
$title = "$total is $additive% of $subtotal";
}
else {
$type = ucfirst($type);
$title = "Subtotal: \$$subtotal; $type: \$$additive; Total: \$$total";
}
my ($percentage, $bill_amount) = @_;
return $title,
structured_answer => {
data => {
title => "$title",
return '', structured_answer => {
data => {
title => "Tip Calculator",
percentage => "$percentage",
bill => "$bill_amount",
},
templates => {
group => 'text',
options => {
content => 'DDH.tips.content'
},
templates => {
group => 'text'
}
};
}
};
}
sub build_test {test_zci(make_structured_answer(@_))}
ddg_goodie_test(
[qw( DDG::Goodie::Tips)],
'20% tip on $20' => build_test('tip', '20.00', '4.00', '24.00'),
'20% tip on $20 bill' => build_test('tip', '20.00', '4.00', '24.00'),
'20% tip for a $20 bill' => build_test('tip', '20.00', '4.00', '24.00'),
'20 percent tip on $20' => build_test('tip', '20.00', '4.00', '24.00'),
'20% tip on $21.63' => build_test('tip', '21.63', '4.33', '25.96'),
'20 percent tip for a $20 bill' => build_test('tip', '20.00', '4.00', '24.00'),
'20 percent tip for a $2000 bill' => build_test('tip', '2,000.00', '400.00', '2,400.00'),
'20% tax on $20' => build_test('tax', '20.00', '4.00', '24.00'),
'25 percent of 20000' => build_test('percentage', '20,000', '25', '5,000'),
'2% of 25,000' => build_test('percentage', '25,000', '2', '500'),
'2% of $25,000' => build_test('percentage', '$25,000', '2', '$500.00'),
'2,000% of -2' => build_test('percentage', '-2', '2,000', '-40'),
'20% tip on $20' => build_test('20', '20'),
'20% tip on $20 bill' => build_test('20', '20'),
'20% tip for a $20 bill' => build_test('20', '20'),
'20 percent tip on $20' => build_test('20', '20'),
'20% tip on $21.63' => build_test('20', '21.63'),
'20 percent tip for a $20 bill' => build_test('20', '20'),
'20 percent tip for a $2000 bill' => build_test('20', '2000'),
'tip calculator' => build_test('', ''), # undef stringified
'calculate tip' => build_test('', ''), # undef stringified
# queries that will be handled by the calc
'25 percent of 20000' => undef,
'2% of 25,000' => undef,
'2% of $25,000' => undef,
'2,000% of -2' => undef,
'20% tax on $20' => undef,
# random. definately shouldn't trigger this IA
'best of 5' => undef,
'4 of 5 dentists' => undef,
'yo, give me some tips' => undef,
'tips to save cash' => undef,
'show me the tip calculator, bro' => undef,
);
done_testing;