diff --git a/lib/DDG/Goodie/Tips.pm b/lib/DDG/Goodie/Tips.pm
index ad0f086ab..cc78233b8 100644
--- a/lib/DDG/Goodie/Tips.pm
+++ b/lib/DDG/Goodie/Tips.pm
@@ -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 (/^(?
$number_re)(?: ?%| percent) (?:(?(?tip|tax) (?:on|for|of))|of)(?: an?)? (?[\$\-]?)(?$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 (/^(?$number_re)(?: ?%| percent) (?:(?(?tip) (?:on|for|of))|of)(?: an?)? (?[\$\-]?)(?$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;
\ No newline at end of file
diff --git a/share/goodie/tips/content.handlebars b/share/goodie/tips/content.handlebars
new file mode 100644
index 000000000..f0208092b
--- /dev/null
+++ b/share/goodie/tips/content.handlebars
@@ -0,0 +1,33 @@
+
diff --git a/share/goodie/tips/tips.css b/share/goodie/tips/tips.css
new file mode 100644
index 000000000..95e87e7db
--- /dev/null
+++ b/share/goodie/tips/tips.css
@@ -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;
+}
diff --git a/share/goodie/tips/tips.js b/share/goodie/tips/tips.js
new file mode 100644
index 000000000..f8683dca5
--- /dev/null
+++ b/share/goodie/tips/tips.js
@@ -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);
diff --git a/t/Tips.t b/t/Tips.t
index 5cc04aeae..d134e9a42 100644
--- a/t/Tips.t
+++ b/t/Tips.t
@@ -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;