diff options
Diffstat (limited to 'httemplate')
25 files changed, 645 insertions, 282 deletions
diff --git a/httemplate/browse/discount.html b/httemplate/browse/discount.html index d3cf873d0..9b2298ae4 100644 --- a/httemplate/browse/discount.html +++ b/httemplate/browse/discount.html @@ -8,8 +8,9 @@ 'count_query' => 'SELECT COUNT(*) FROM discount', 'disableable' => 1, 'disabled_statuspos' => 1, - 'header' => [ 'Name', 'Class', 'Discount', ], + 'header' => [ 'Name', 'Comment', 'Class', 'Discount', ], 'fields' => [ 'name', + 'comment', 'classname', 'description', ], diff --git a/httemplate/docs/about.html b/httemplate/docs/about.html index 80d9488b6..0f173f228 100644 --- a/httemplate/docs/about.html +++ b/httemplate/docs/about.html @@ -56,7 +56,7 @@ GNU <b>Affero</b> General Public License.<BR> % unless ( $agentnum ) { <CENTER> - <FONT SIZE="-3">"" - R. Hunter</FONT> + <FONT SIZE="-3">"Half the world's a desert / Cannibals eat human brains for dessert" - D. Zero</FONT> </CENTER> % } diff --git a/httemplate/docs/credits.html b/httemplate/docs/credits.html index 158c5ba2d..b5ed451bc 100644 --- a/httemplate/docs/credits.html +++ b/httemplate/docs/credits.html @@ -58,6 +58,7 @@ Charles A. Beasley<BR> Stephen Bechard<BR> Eric Bosrup<BR> Dickie Bradford<BR> +Alex Brelsfoard<BR> Dave Burgess<BR> Joe Camadine<BR> Chris Cappuccio<BR> @@ -91,6 +92,7 @@ Mack Nagashima<BR> David Peters<BR> Matt Peterson<BR> Luke Pfeifer<BR> +Jonathan Prykop<BR> Ricardo Signes<BR> Steve Simitzis<BR> Stanislav Sinyagin<BR> diff --git a/httemplate/edit/cust_main-contacts.html b/httemplate/edit/cust_main-contacts.html index 9f0654608..3b7eb07d3 100644 --- a/httemplate/edit/cust_main-contacts.html +++ b/httemplate/edit/cust_main-contacts.html @@ -11,6 +11,7 @@ { 'field' => 'contactnum', 'type' => 'contact', 'colspan' => 6, + 'custnum' => $custnum, 'm2m_method' => 'cust_contact', 'm2m_dstcol' => 'contactnum', 'm2_label' => ' ', #'Contact', diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi index 353ae1799..da87bfca7 100755 --- a/httemplate/edit/cust_main.cgi +++ b/httemplate/edit/cust_main.cgi @@ -325,8 +325,8 @@ if ( $cgi->param('error') ) { $cust_main->company( $prospect_main->company ); #first contact? -> name - my @contacts = $prospect_main->contact; - my $contact = $contacts[0]; + my @prospect_contacts = $prospect_main->prospect_contact; + my $contact = $prospect_contacts[0]->contact; $cust_main->first( $contact->first ); $cust_main->set( 'last', $contact->get('last') ); #contact phone numbers? diff --git a/httemplate/edit/cust_main/contacts_new.html b/httemplate/edit/cust_main/contacts_new.html index f59126a66..0ab02b420 100644 --- a/httemplate/edit/cust_main/contacts_new.html +++ b/httemplate/edit/cust_main/contacts_new.html @@ -11,6 +11,7 @@ { 'field' => 'contactnum', 'type' => 'contact', 'colspan' => 6, + 'custnum' => $opt{cust_main}->custnum, 'm2m_method' => 'cust_contact', 'm2m_dstcol' => 'contactnum', 'm2_label' => 'Contact', @@ -36,8 +37,8 @@ my $m2_error_callback = sub { my($cgi, $object) = @_; #process_o2m fields in process/cust_main-contacts.html - my @fields = qw( first last title comment ); - my @gfields = ( '', map "_$_", @fields ); + my $fields = FS::contact->cgi_contact_fields; + my @gfields = ( '', map "_$_", @$fields ); map { if ( /^contactnum(\d+)$/ ) { @@ -45,7 +46,7 @@ my $m2_error_callback = sub { if ( grep $cgi->param("contactnum$num$_"), @gfields ) { my $x = new FS::contact { 'contactnum' => scalar($cgi->param("contactnum$num")), - map { $_ => scalar($cgi->param("contactnum${num}_$_")) } @fields, + map { $_ => scalar($cgi->param("contactnum${num}_$_")) } @$fields, }; $x; } else { diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html index 9e506a731..4d5beee71 100644 --- a/httemplate/edit/elements/edit.html +++ b/httemplate/edit/elements/edit.html @@ -334,6 +334,10 @@ Example: % #any? % 'colspan' => $f->{'colspan'}, % 'required' => $f->{'required'}, +% +% #contact +% 'custnum' => $f->{'custnum'}, +% 'prospectnum' => $f->{'prospectnum'}, % ); % % $include_common{$_} = $f->{$_} foreach grep exists($f->{$_}), diff --git a/httemplate/edit/elements/part_svc_column.html b/httemplate/edit/elements/part_svc_column.html index 53cda859e..2bb4f5e41 100644 --- a/httemplate/edit/elements/part_svc_column.html +++ b/httemplate/edit/elements/part_svc_column.html @@ -140,7 +140,8 @@ that field. 'value_col' => $def->{'select_key'}, 'order_by' => dbdef->table($def->{'select_table'})->primary_key, 'multiple' => $def->{'multiple'}, - 'disable_empty' => 1, + 'disable_empty' => $def->{'select_allow_empty'} ? undef : 1, + 'empty_label' => $def->{'select_allow_empty'} ? ' ' : undef, 'curr_value' => $value, # these can be switched between multiple and singular, # so put the complete curr_value in an attribute diff --git a/httemplate/edit/quick-charge.html b/httemplate/edit/quick-charge.html index 20a9ec783..58c1b0a82 100644 --- a/httemplate/edit/quick-charge.html +++ b/httemplate/edit/quick-charge.html @@ -106,7 +106,7 @@ function bill_now_changed (what) { <TABLE ID="QuickChargeTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 STYLE="background-color: #cccccc"> -% if ( $cust_pkg ) { +% if ( $cust_pkg ) { #modify one-time charge <INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $cust_pkg->pkgnum %>"> % my $field = '/elements/tr-input-text.html'; @@ -171,6 +171,7 @@ function bill_now_changed (what) { &> % } +% unless ($billed) { <TR> <TD ALIGN="right"><% mt('Tax exempt') |h %> </TD> <TD><INPUT TYPE="checkbox" NAME="setuptax" VALUE="Y" <% $cgi->param('setuptax') ? 'CHECKED' : '' %>></TD> @@ -179,6 +180,7 @@ function bill_now_changed (what) { <& /elements/tr-select-taxclass.html, 'curr_value' => $part_pkg->get('taxclass') &> <& /elements/tr-select-taxproduct.html, 'label' => emt('Tax product'), 'onclick' => 'parent.taxproductmagic(this);', 'curr_value' => $part_pkg->get('taxproductnum') &> +% } % } else { # new one-time charge diff --git a/httemplate/elements/change_history_common.html b/httemplate/elements/change_history_common.html index 34ce70b6c..9e32bef33 100644 --- a/httemplate/elements/change_history_common.html +++ b/httemplate/elements/change_history_common.html @@ -172,6 +172,18 @@ my $svc_labelsub = sub { $label. ': <b>'. encode_entities($item->label($item->history_date)). '</b>'; }; +my $discounts = {}; +my $discount_labelsub = sub { + my($item, $label) = @_; + my $dnum = $item->discountnum; + $discounts->{$dnum} ||= qsearchs({ + 'table'=>'discount', + 'hashref'=>{'discountnum'=>$dnum} + }); + my $d = $discounts->{$dnum}; + $label . ': <b>' . encode_entities($d->description_short) . '<b>'; +}; + my %h_table_labelsub = ( 'h_cust_pkg' => $pkg_labelsub, 'h_svc_acct' => $svc_labelsub, @@ -183,6 +195,7 @@ my %h_table_labelsub = ( 'h_svc_external' => $svc_labelsub, 'h_svc_phone' => $svc_labelsub, #'h_phone_device' + 'h_cust_pkg_discount' => $discount_labelsub, ); my $cust_pkg_date_format = '%b %o, %Y'; diff --git a/httemplate/elements/contact.html b/httemplate/elements/contact.html index 979c26b49..ef74481c0 100644 --- a/httemplate/elements/contact.html +++ b/httemplate/elements/contact.html @@ -9,7 +9,7 @@ <SELECT NAME="<%$name%>_classnum" <% $onchange %>> <OPTION VALUE=""> % my $classnum = scalar($cgi->param($name.'_classnum')) -% || $contact->classnum; +% || $X_contact->classnum; % foreach my $contact_class (@contact_class) { <OPTION VALUE="<% $contact_class->classnum %>" <% ($contact_class->classnum == $classnum) ? 'SELECTED' : '' %> @@ -40,6 +40,8 @@ % } % } elsif ( $field eq 'emailaddress' ) { % $value = join(', ', map $_->emailaddress, $contact->contact_email); +% } elsif ( $field eq 'selfservice_access' || $field eq 'comment' ) { +% $value = $X_contact->get($field); % } else { % $value = $contact->get($field); % } @@ -100,10 +102,25 @@ if ( $opt{'onchange'} ) { my @contact_class = qsearch('contact_class', { 'disabled' => '' }); my $contact; +my $X_contact; if ( $curr_value ) { $contact = qsearchs('contact', { 'contactnum' => $curr_value } ); + if ( $opt{'custnum'} ) { + $X_contact = qsearchs('cust_contact', { + 'contactnum' => $curr_value, + 'custnum' => $opt{'custnum'}, + }); + } elsif ( $opt{'prospectnum'} ) { + $X_contact = qsearchs('prospect_contact', { + 'contactnum' => $curr_value, + 'prospectnum' => $opt{'prospectnum'}, + }); + } else { + die 'neither custnum nor prospectnum specified'; + } } else { $contact = new FS::contact {}; + $X_contact = new FS::cust_contact; #arbitrary, it could be prospect_contact } my %size = ( 'title' => 12 ); diff --git a/httemplate/elements/masked_input_1.1.js b/httemplate/elements/masked_input_1.1.js deleted file mode 100644 index 05efa779b..000000000 --- a/httemplate/elements/masked_input_1.1.js +++ /dev/null @@ -1,195 +0,0 @@ -/*********************************************************************** - Masked Input version 1.1 -************************************************************************ -Author: Kendall Conrad -Home page: http://www.angelwatt.com/coding/masked_input.php -Created: 2008-12-16 -Modified: 2010-04-14 -Description: -License: This work is licensed under a Creative Commons Attribution-Share Alike - 3.0 United States License http://creativecommons.org/licenses/by-sa/3.0/us/ - -Argument pieces: -- elm: [req] text input node to apply the mask on -- format: [req] string format for the mask -- allowed: [opt, '0123456789'] string with chars allowed to be typed -- sep: [opt, '\/:-'] string of char(s) used as separators in mask -- typeon: [opt, '_YMDhms'] string of chars in mask that can be typed on -- onbadkey: [opt, null] function to run when user types a unallowed key -- badkeywait: [opt, 0] used with onbadkey. Indicates how long (in ms) to lock - text input for onbadkey function to run -***********************************************************************/ -function MaskedInput(args) -{ - if (args['elm'] === null || args['format'] === null) { return false; } - var el = args['elm'], - format = args['format'], - allowed = args['allowed'] || '0123456789', - sep = args['separator'] || '\/:-', - open = args['typeon'] || '_YMDhms', - onbadkey = args['onbadkey'] || function(){}, - badwait = args['badkeywait'] || 0; - - var locked = false, hold = 0; - el.value = format; - // Assign events - el.onkeydown = KeyHandlerDown; // - el.onkeypress = KeyHandlerPress; // add event handlers to element - el.onkeyup = KeyHandlerUp; // - - function GetKey(code) - { - code = code || window.event, ch = ''; - var keyCode = code.which, evt = code.type; - if (keyCode == null) { keyCode = code.keyCode; } - if (keyCode === null) { return ''; } // no key, no play - // deal with special keys - switch (keyCode) { - case 8: ch = 'bksp'; break; - case 46: // handle del and . both being 46 - ch = (evt == 'keydown') ? 'del' : '.'; break; - case 16: ch = 'shift'; break;//shift - case 0:/*CRAP*/ case 9:/*TAB*/ case 13:/*ENTER*/ - ch = 'etc'; break; - case 37: case 38: case 39: case 40: // arrow keys - ch = (!code.shiftKey && - (code.charCode != 39 && code.charCode !== undefined)) ? - 'etc' : String.fromCharCode(keyCode); - break; - // default to thinking it's a character or digit - default: ch = String.fromCharCode(keyCode); - } - return ch; - } - function KeyHandlerDown(e) - { - e = e || event; - if (locked) { return false; } - var key = GetKey(e); - if (el.value == '') { el.value = format; SetTextCursor(el,0); } - // Only do update for bksp del - if (key == 'bksp' || key == 'del') { Update(key); return false; } - else if (key == 'etc' || key == 'shift') { return true; } - else { return true; } - } - function KeyHandlerPress(e) - { - e = e || event; - if (locked) { return false; } - var key = GetKey(e); - // Check if modifier key is being pressed; command - if (key=='etc' || e.metaKey || e.ctrlKey || e.altKey) { return true; } - if (key != 'bksp' && key != 'del' && key != 'etc' && key != 'shift') { - if (!GoodOnes(key)) { return false; } - return Update(key); - } - else { return false; } - } - function KeyHandlerUp(e) { hold = 0; } - function Update(key) - { - var p = GetTextCursor(el), c = el.value, val = ''; - // Handle keys now - switch (true) { - case (allowed.indexOf(key) != -1): - if (++p > format.length) { return false; } // if text csor at end - // Handle cases where user places csor before separator - while (sep.indexOf(c.charAt(p-1)) != -1 && p <= format.length) { p++; } - val = c.substr(0, p-1) + key + c.substr(p); - // Move csor up a spot if next char is a separator char - if (allowed.indexOf(c.charAt(p)) == -1 - && open.indexOf(c.charAt(p)) == -1) { p++; } - break; - case (key=='bksp'): // backspace - if (--p < 0) return false; // at start of field - // If previous char is a separator, move a little more - while (allowed.indexOf(c.charAt(p)) == -1 - && open.indexOf(c.charAt(p)) == -1 - && p > 1) { p--; } - val = c.substr(0, p) + format.substr(p,1) + c.substr(p+1); - break; - case (key=='del'): // forward delete - if (p >= c.length) { return false; } // at end of field - // If next char is a separator and not the end of the text field - while (sep.indexOf(c.charAt(p)) != -1 - && c.charAt(p) != '') { p++; } - val = c.substr(0, p) + format.substr(p,1) + c.substr(p+1); - p++; // Move position forward - break; - case (key=='etc'): return true; // Catch other allowed chars - default: return false; // Ignore the rest - } - el.value = ''; // blank it first (Firefox issue) - el.value = val; // put updated value back in - SetTextCursor(el, p); // Set the text cursor - return false; - } - function GetTextCursor(node) - { - try { - if (node.selectionStart >= 0) { return node.selectionStart; } - else if (document.selection) {// IE - var ntxt = node.value; // getting starting text - var rng = document.selection.createRange(); - rng.text = '|%|'; - var start = node.value.indexOf('|%|'); - rng.moveStart('character', -3); - rng.text = ''; - // put starting text back in, - // fixes issue if all text was highlighted - node.value = ntxt; - return start; - } return -1; - } catch(e) { return false; } - } - function SetTextCursor(node, pos) - { - try { - if (node.selectionStart) { - node.focus(); - node.setSelectionRange(pos,pos); - } - else if (node.createTextRange) { // IE - var rng = node.createTextRange(); - rng.move('character', pos); - rng.select(); - } - } catch(e) { return false; } - } - function GoodOnes(k) - { - if (allowed.indexOf(k) == -1 && k!='bksp' && k!='del' && k!='etc') { - var p = GetTextCursor(el); // Need to ensure cursor position not lost - locked = true; onbadkey(); - // Hold lock long enough for onbadkey function to run - setTimeout(function(){locked=false; SetTextCursor(el,p);}, badwait); - return false; - } return true; - } - function resetField() { - el.value = format; - } - function setAllowed(a) { - allowed = a; - resetField(); - } - function setFormat(f) { - format = f; - resetField(); - } - function setSeparator(s) { - sep = s; - resetField(); - } - function setTypeon(t) { - open = t; - resetField(); - } - return { - resetField:resetField, - setAllowed:setAllowed, - setFormat:setFormat, - setSeparator:setSeparator, - setTypeon:setTypeon - } -} diff --git a/httemplate/elements/masked_input_1.3.js b/httemplate/elements/masked_input_1.3.js new file mode 100644 index 000000000..54e38ac86 --- /dev/null +++ b/httemplate/elements/masked_input_1.3.js @@ -0,0 +1,462 @@ +/** + * AW Masked Input + * @version 1.3 + * @author Kendall Conrad + * @url http://www.angelwatt.com/coding/masked_input.php + * @created 2008-12-16 + * @modified 2013-08-19 + * @license This work is licensed under a Creative Commons + * Attribution-Share Alike 3.0 United States License + * http://creativecommons.org/licenses/by-sa/3.0/us/ + * + * @param scope The object to attach MaskedInput to. + */ +(function(scope) { + 'use strict'; + + /** + * MaskedInput takes many possible arguments described below. + * Note: req = required, opt = optional + * @param {object} args { + * -elm [req] text input node to apply the mask on + * -format [req] string format for the mask + * -allowed [opt, '0123456789'] string with chars allowed to be typed + * -sep [opt, '\/:-'] string of char(s) used as separators in mask + * -typeon [opt, '_YMDhms'] string of chars in mask that can be typed on + * -onfilled [opt, null] function to run when the format is filled in + * -onbadkey [opt, null] function to run when user types a unallowed key + * -badkeywait [opt, 0] used with onbadkey. Indicates how long (in ms) + * to lock text input for onbadkey function to run + * -preserve [opt, true] whether to preserve existing text in + * field during init. + * } + * @returns MaskedInput + */ + scope.MaskedInput = function(args) { + // Ensure passing in valid argument + if (!args || !args.elm || !args.format) { + return null; + } + // Ensure use of 'new' + if (!(this instanceof scope.MaskedInput)) { + return new scope.MaskedInput(args); + } + // Initialize variables + var self = this, + el = args.elm, + format = args.format, + allowed = args.allowed || '0123456789', + sep = args.separator || '\/:-', + open = args.typeon || '_YMDhms', + onbadkey = args.onbadkey || function() {}, + onfilled = args.onfilled || function() {}, + badwait = args.badkeywait || 0, + preserve = args.hasOwnProperty('preserve') ? !!args.preserve : true, + // ---- + enabled = true, + locked = false, + startText = format, + /** + * Add events to objects. + */ + evtAdd = (function() { + if (window.addEventListener) { + return function(obj, type, fx, capture) { + obj.addEventListener(type, fx, + (capture === undefined) ? false : capture); + }; + } + if (window.attachEvent) { + return function(obj, type, fx) { + obj.attachEvent('on' + type, fx); + }; + } + return function(obj, type, fx) { + obj['on' + type] = fx; + }; + }()), + /** + * Checks whether the format has been completely filled out. + * @return boolean if all typeon chars have been filled. + */ + isFilled = function() { + // Check if any typeon characters are left + // Work from end of string as it's usually last filled + for (var a = el.value.length - 1; a >= 0; a--) { + // Check against each typeon character + for (var c = 0, d = open.length; c < d; c++) { + // If one matches we don't need to check anymore + if (el.value[a] === open[c]) { + return false; + } + } + } + return true; + }, + /** + * Gets the current position of the text cursor in a text field. + * @param node a input or textarea HTML node. + * @return int text cursor position index, or -1 if there was a problem. + */ + getTextCursor = function(node) { + try { + node.focus(); + if (node.selectionStart >= 0) { + return node.selectionStart; + } + if (document.selection) {// IE + var rng = document.selection.createRange(); + return -rng.moveStart('character', -node.value.length); + } + return -1; + } + catch (e) { + return -1; + } + }, + /** + * Sets the text cursor in a text field to a specific position. + * @param node a input or textarea HTML node. + * @param pos int of the position to be placed. + * @return boolean true is successful, false otherwise. + */ + setTextCursor = function(node, pos) { + try { + if (node.selectionStart) { + node.focus(); + node.setSelectionRange(pos, pos); + } + else if (node.createTextRange) { // IE + var rng = node.createTextRange(); + rng.move('character', pos); + rng.select(); + } + } + catch (e) { + return false; + } + return true; + }, + /** + * Gets the keyboard input in usable way. + * @param code integer character code + * @return string representing character code + */ + getKey = function(code) { + code = code || window.event; + var ch = '', + keyCode = code.which, + evt = code.type; + if (keyCode === undefined || keyCode === null) { + keyCode = code.keyCode; + } + // no key, no play + if (keyCode === undefined || keyCode === null) { + return ''; + } + // deal with special keys + switch (keyCode) { + case 8: + ch = 'bksp'; + break; + case 46: // handle del and . both being 46 + ch = (evt === 'keydown') ? 'del' : '.'; + break; + case 16: + ch = 'shift'; + break; + case 0: /*CRAP*/ + case 9: /*TAB*/ + case 13:/*ENTER*/ + ch = 'etc'; + break; + case 37: + case 38: + case 39: + case 40: // arrow keys + ch = (!code.shiftKey && + (code.charCode !== 39 && code.charCode !== undefined)) ? + 'etc' : String.fromCharCode(keyCode); + break; + // default to thinking it's a character or digit + default: + ch = String.fromCharCode(keyCode); + break; + } + return ch; + }, + /** + * Stop the event propogation chain. + * @param evt Event to stop + * @param ret boolean, used for IE to prevent default event + */ + stopEvent = function(evt, ret) { + // Stop default behavior the standard way + if (evt.preventDefault) { + evt.preventDefault(); + } + // Then there's IE + evt.returnValue = ret || false; + }, + /** + * Updates the text field with the given key. + * @param key string keyboard input. + */ + update = function(key) { + var p = getTextCursor(el), + c = el.value, + val = '', + cond = true; + // Handle keys now + switch (cond) { + // Allowed characters + case (allowed.indexOf(key) !== -1): + p = p + 1; + // if text cursor at end + if (p > format.length) { + return false; + } + // Handle cases where user places cursor before separator + while (sep.indexOf(c.charAt(p - 1)) !== -1 && p <= format.length) { + p = p + 1; + } + val = c.substr(0, p - 1) + key + c.substr(p); + // Move csor up a spot if next char is a separator char + if (allowed.indexOf(c.charAt(p)) === -1 + && open.indexOf(c.charAt(p)) === -1) { + p = p + 1; + } + break; + case (key === 'bksp'): // backspace + p = p - 1; + // at start of field + if (p < 0) { + return false; + } + // If previous char is a separator, move a little more + while (allowed.indexOf(c.charAt(p)) === -1 + && open.indexOf(c.charAt(p)) === -1 + && p > 1) { + p = p - 1; + } + val = c.substr(0, p) + format.substr(p, 1) + c.substr(p + 1); + break; + case (key === 'del'): // forward delete + // at end of field + if (p >= c.length) { + return false; + } + // If next char is a separator and not the end of the text field + while (sep.indexOf(c.charAt(p)) !== -1 + && c.charAt(p) !== '') { + p = p + 1; + } + val = c.substr(0, p) + format.substr(p, 1) + c.substr(p + 1); + p = p + 1; // Move position forward + break; + case (key === 'etc'): + // Catch other allowed chars + return true; + default: + return false; // Ignore the rest + } + el.value = ''; // blank it first (Firefox issue) + el.value = val; // put updated value back in + setTextCursor(el, p); // Set the text cursor + return false; + }, + /** + * Returns whether or not a given input is valid for the mask. + * @param k string of character to check. + * @return bool true if it's a valid character. + */ + goodOnes = function(k) { + // if not in allowed list, or invisible key action + if (allowed.indexOf(k) === -1 && k !== 'bksp' && k !== 'del' && k !== 'etc') { + // Need to ensure cursor position not lost + var p = getTextCursor(el); + locked = true; + onbadkey(k); + // Hold lock long enough for onbadkey function to run + setTimeout(function() { + locked = false; + setTextCursor(el, p); + }, badwait); + return false; + } + return true; + }, + /** + * Handles the key down events. + * @param e Event + */ + keyHandlerDown = function(e) { + if (!enabled) { + return true; + } + if (locked) { + stopEvent(e); + return false; + } + e = e || event; + var key = getKey(e); + // Stop copy and paste + if ((e.metaKey || e.ctrlKey) && (key === 'X' || key === 'V')) { + stopEvent(e); + return false; + } + // Allow for OS commands + if (e.metaKey || e.ctrlKey) { + return true; + } + if (el.value === '') { + el.value = format; + setTextCursor(el, 0); + } + // Only do update for bksp del + if (key === 'bksp' || key === 'del') { + update(key); + stopEvent(e); + return false; + } + return true; + }, + /** + * Handles the key press events. + * @param e Event + */ + keyHandlerPress = function(e) { + if (!enabled) { + return true; + } + if (locked) { + stopEvent(e); + return false; + } + e = e || event; + var key = getKey(e); + // Check if modifier key is being pressed; command + if (key === 'etc' || e.metaKey || e.ctrlKey || e.altKey) { + return true; + } + if (key !== 'bksp' && key !== 'del' && key !== 'shift') { + if (!goodOnes(key)) { + stopEvent(e); + return false; + } + if (update(key)) { + if (isFilled()) { + onfilled(); + } + stopEvent(e, true); + return true; + } + if (isFilled()) { + onfilled(); + } + stopEvent(e); + return false; + } + return false; + }, + /** + * Initialize the object. + */ + init = function() { + // Check if an input or textarea tag was passed in + if (!el.tagName || (el.tagName.toUpperCase() !== 'INPUT' + && el.tagName.toUpperCase() !== 'TEXTAREA')) { + return null; + } + // Only place formatted text in field when not preserving + // text or it's empty. + if (!preserve || el.value === '') { + el.value = format; + } + // Assign events + evtAdd(el, 'keydown', function(e) { + keyHandlerDown(e); + }); + evtAdd(el, 'keypress', function(e) { + keyHandlerPress(e); + }); + // Let us set the initial text state when focused + evtAdd(el, 'focus', function() { + startText = el.value; + }); + // Handle onChange event manually + evtAdd(el, 'blur', function() { + if (el.value !== startText && el.onchange) { + el.onchange(); + } + }); + return self; + }; + + /** + * Resets the text field so just the format is present. + */ + self.resetField = function() { + el.value = format; + }; + + /** + * Set the allowed characters that can be used in the mask. + * @param a string of characters that can be used. + */ + self.setAllowed = function(a) { + allowed = a; + self.resetField(); + }; + + /** + * The format to be used in the mask. + * @param f string of the format. + */ + self.setFormat = function(f) { + format = f; + self.resetField(); + }; + + /** + * Set the characters to be used as separators. + * @param s string representing the separator characters. + */ + self.setSeparator = function(s) { + sep = s; + self.resetField(); + }; + + /** + * Set the characters that the user will be typing over. + * @param t string representing the characters that will be typed over. + */ + self.setTypeon = function(t) { + open = t; + self.resetField(); + }; + + /** + * Sets whether the mask is active. + */ + self.setEnabled = function(enable) { + enabled = enable; + }; + + /** + * Local change for Freeside: sets the content of the field, + * respecting formatting rules + */ + self.setValue = function(value) { + self.resetField(); + setTextCursor(el, 0); + var i = 0; // index in value + while (i < value.length && !isFilled()) { + update(value[i]); + i++; + } + } + + return init(); + }; +}(window)); diff --git a/httemplate/elements/popup_link.html b/httemplate/elements/popup_link.html index e5f8c61ca..2b6b187e9 100644 --- a/httemplate/elements/popup_link.html +++ b/httemplate/elements/popup_link.html @@ -2,9 +2,9 @@ Example: - include('/elements/init_overlib.html') + <& /elements/init_overlib.html &> - include( '/elements/popup_link.html', { #hashref or a list, either way is fine + <& /elements/popup_link.html', { #hashref or a list, either way is fine #required 'action' => 'content.html', # uri for content of popup @@ -23,7 +23,8 @@ Example: 'aname' => "target", # link NAME= value, useful for #targets 'target' => '_parent', 'style' => 'css-attribute:value', - } ) + } + &> </%doc> % if ($params->{'action'} && $label) { diff --git a/httemplate/elements/selectlayers.html b/httemplate/elements/selectlayers.html index cb1d2d619..785ee369e 100644 --- a/httemplate/elements/selectlayers.html +++ b/httemplate/elements/selectlayers.html @@ -121,7 +121,7 @@ Example: <OPTION VALUE="<% $option %>" <% $option eq $selected ? ' SELECTED' : '' %> - ><% $options->{$option} %></OPTION> + ><% $options->{$option} |h %></OPTION> % } diff --git a/httemplate/elements/tr-fixed.html b/httemplate/elements/tr-fixed.html index 6904e3b30..373c0ab3a 100644 --- a/httemplate/elements/tr-fixed.html +++ b/httemplate/elements/tr-fixed.html @@ -1,6 +1,6 @@ <% include('tr-td-label.html', @_ ) %> - <TD BGCOLOR="#dddddd" <% $style %>><% $value %></TD> + <TD BGCOLOR="#dddddd" <% $style %> <% $colspan %>><% $value %></TD> </TR> @@ -10,7 +10,9 @@ my %opt = @_; -my $style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : ''; +my $style = $opt{'cell_style'} ? ' STYLE="'. $opt{'cell_style'}. '" ' : ''; + +my $colspan = $opt{'colspan'} ? ' COLSPAN="'. $opt{'colspan'}. '" ' : ''; my $value = $opt{'formatted_value'} || $opt{'curr_value'} || $opt{'value'}; $value = $opt{'prefix'} . $value if defined($opt{'prefix'}); diff --git a/httemplate/elements/tr-input-mask.html b/httemplate/elements/tr-input-mask.html index 19942b58b..fdd20962d 100644 --- a/httemplate/elements/tr-input-mask.html +++ b/httemplate/elements/tr-input-mask.html @@ -1,52 +1,62 @@ % if ( !$init ) { -<script type="text/javascript" src="<%$p%>elements/masked_input_1.1.js"> +<script type="text/javascript" src="<%$p%>elements/masked_input_1.3.js"> </script> % $init++; % } <& /elements/tr-input-text.html, id => $id, @_ &> <script type="text/javascript"> <&| /elements/onload.js &> -MaskedInput({ - elm: document.getElementById('<%$id%>'), +var el = document.getElementById('<%$id%>'); +el.MaskedInput = window.MaskedInput({ + elm: el, format: '<% $opt{format} %>', <% $opt{allowed} ? "allowed: '$opt{allowed}'," : '' %> <% $opt{typeon} ? "typeon: '$opt{typeon}'," : '' %> }); -document.getElementById('<%$id%>').value = <% $value |js_string %>; +el.value = <% $value |js_string %>; % if ( $clipboard_hack ) { -var t = document.getElementById('<% $id %>'); var container = document.getElementById('<%$id%>_clipboard'); -var KeyHandlerDown = t.onkeydown -t.onkeydown = function(e) { - if (typeof(e) == 'undefined') { - // ie8 hack - e = event; - } +var KeyDownHandler = function(e) { + e = e || event; // IE8 // intercept ctrl-c and ctrl-x // and cmd-c and cmd-x on mac - // when text is selected if ( ( e.ctrlKey || e.metaKey ) ) { - // do the dance - var separators = /[\\/:-]/g; - var s = t.value.substr(t.selectionStart, t.selectionEnd); - if ( s ) { - container.value = s.replace(separators, ''); - container.previous = t; - container.focus(); - container.select(); - return true; + // grab contents of the field, strip out delimiters and copy to container, + // and select its contents so that the next "ctrl-c" copies it + + el.select(); // just a visual hint to the user + var reject = /[^A-Za-z0-9]/g; + container.value = el.value.replace(reject, ''); + container.focus(); + container.select(); + // don't confuse the maskedinput key handlers by letting them see this + if (e.stopImmediatePropagation) { + e.stopImmediatePropagation(); + } else { + // IE8 + e.returnValue = false; + e.cancelBubble = true; } } - return KeyHandlerDown.call(t, e); }; -container.onkeyup = function(e) { - if ( container.previous ) { - setTimeout(function() { - //container.previous.value = container.value; - container.previous.focus(); - }, 10); - } +var KeyUpHandler = function(e) { + e = e || event; + setTimeout( function() { el.focus() } , 10); return true; +}; +var PasteHandler = function(e) { + setTimeout( function() { + el.MaskedInput.setValue(container.value); + }, 10); +}; +if ( el.addEventListener ) { + el.addEventListener('keydown', KeyDownHandler); + container.addEventListener('keyup', KeyUpHandler); + container.addEventListener('paste', PasteHandler); +} else if ( el.attachEvent ) { + el.attachEvent('onkeydown', KeyDownHandler); + container.attachEvent('onkeyup', KeyUpHandler); + container.attachEvent('onpaste', PasteHandler); } % } # clipboard hack </&> diff --git a/httemplate/elements/tr-select-contact.html b/httemplate/elements/tr-select-contact.html index e37d26d1b..4eb8a9879 100644 --- a/httemplate/elements/tr-select-contact.html +++ b/httemplate/elements/tr-select-contact.html @@ -138,8 +138,8 @@ if ( $cgi->param('error') ) { if ( length($opt{'curr_value'}) ) { $contactnum = $opt{'curr_value'}; } elsif ($prospect_main) { - my @cust_contact = $prospect_main->contact; - $contactnum = $cust_contact[0]->contactnum if scalar(@cust_contact)==1; + my @prospect_contact = $prospect_main->prospect_contact; + $contactnum = $prospect_contact[0]->contactnum if scalar(@prospect_contact)==1; } else { #$cust_main $cgi->param('contactnum') =~ /^(\-?\d*)$/ or die "illegal contactnum"; $contactnum = $1; @@ -176,8 +176,10 @@ my $contact_sort = sub { }; my @contact; -push @contact, $cust_main->cust_contact if $cust_main; -push @contact, $prospect_main->contact if $prospect_main; +push @contact, map $_->contact, $cust_main->cust_contact + if $cust_main; +push @contact, map $_->contact, $prospect_main->prospect_contact + if $prospect_main; push @contact, $contact if !$cust_main && $contact && $contact->contactnum > 0 && ! grep { $_->contactnum == $contact->contactnum } @contact; diff --git a/httemplate/elements/tr-select-cust_location.html b/httemplate/elements/tr-select-cust_location.html index abaaa5b42..7a5b43bb8 100644 --- a/httemplate/elements/tr-select-cust_location.html +++ b/httemplate/elements/tr-select-cust_location.html @@ -287,6 +287,8 @@ if ( $locationnum && $locationnum > 0 ) { $cust_location->coord_auto('Y'); my $location_sort = sub { + #enabled w/label_prefix _location # $a->locationname cmp $b->locationname + # or $a->country cmp $b->country or lc($a->city) cmp lc($b->city) or lc($a->address1) cmp lc($b->address1) diff --git a/httemplate/misc/email-quotation.html b/httemplate/misc/email-quotation.html index b93b80bb7..64e3691b5 100644 --- a/httemplate/misc/email-quotation.html +++ b/httemplate/misc/email-quotation.html @@ -16,9 +16,11 @@ % } % } -% my @contact = $quotation->custnum ? $quotation->cust_main->cust_contact -% : $quotation->prospect_main->contact; -% foreach my $contact ( @contact ) { +% my @X_contact = $quotation->custnum +% ? $quotation->cust_main->cust_contact +% : $quotation->prospect_main->prospect_contact; +% foreach my $X_contact ( @X_contact ) { +% my $contact = $X_contact->contact; % foreach my $contact_email ( $contact->contact_email ) { % $emails++; <& .emailrow, $contact_email->emailaddress, $contact->firstlast &> diff --git a/httemplate/search/contact.html b/httemplate/search/contact.html index 193349369..c3667df98 100644 --- a/httemplate/search/contact.html +++ b/httemplate/search/contact.html @@ -1,13 +1,13 @@ <& elements/search.html, title => 'Contacts', name_singular => 'contact', - query => { select => $select, + query => { select => join(', ', @select), table => 'contact', addl_from => $addl_from, hashref => \%hash, extra_sql => $extra_sql, }, - count_query => "SELECT COUNT(*) FROM contact $extra_sql", #XXX + count_query => "SELECT COUNT(*) FROM contact $addl_from $extra_sql", #XXX header => \@header, fields => \@fields, links => \@links, @@ -17,13 +17,29 @@ die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('List contacts'); -my $select = 'contact.*'; +my @select = 'contact.contactnum AS contact_contactnum'; #if we select it as bare contactnum, the multi-customer listings go away +push @select, map "contact.$_", qw( first last title ); my %hash = (); my $addl_from = ''; -my @header = ( 'First', 'Last', 'Title', ); -my @fields = ( 'first', 'last', 'title', ); -my @links = ( '', '', '' ); +my $link; #for closure in this sub, we'll define it later +my $contact_classname_sub = sub { + my $contact = shift; + my %hash = ( 'contactnum' => $contact->contact_contactnum ); + my $X_contact; + if ( $link eq 'cust_main' ) { + $X_contact = qsearchs('cust_contact', { %hash, 'custnum' => $contact->custnum } ); + } elsif ( $link eq 'prospect_main' ) { + $X_contact = qsearchs('prospect_contact', { %hash, 'prospectnum' => $contact->prospectnum } ); + } else { + die 'guru meditation #5555'; + } + $X_contact->contact_classname; +}; + +my @header = ( 'First', 'Last', 'Title', 'Type' ); +my @fields = ( 'first', 'last', 'title', $contact_classname_sub ); +my @links = ( '', '', '', '', ); my $company_link = ''; @@ -32,22 +48,30 @@ if ( $cgi->param('selfservice_access') eq 'Y' ) { } my $extra_sql = ''; -if ( $cgi->param('link') ) { +$link = $cgi->param('link'); +if ( $link ) { - my $coalesce = ', COALESCE( cust_main.company,'; my $as = ') AS prospect_or_customer'; - if ( $cgi->param('link') eq 'cust_main' ) { + if ( $link eq 'cust_main' ) { push @header, 'Customer'; - $select .= "$coalesce cust_main.first||' '||cust_main.last $as"; - $addl_from = ' LEFT JOIN cust_main USING ( custnum )'; - $extra_sql = ' custnum IS NOT NULL '; + push @select, + "COALESCE( cust_main.company, cust_main.first||' '||cust_main.last $as", + map "cust_contact.$_", qw( custnum classnum comment selfservice_access ); + $addl_from = + ' LEFT JOIN cust_contact USING ( contactnum ) '. + ' LEFT JOIN cust_main ON ( cust_contact.custnum = cust_main.custnum )'; + $extra_sql = ' cust_contact.custnum IS NOT NULL '; $company_link = [ $p.'view/cust_main.cgi?', 'custnum' ]; - } elsif ( $cgi->param('link') eq 'prospect_main' ) { + } elsif ( $link eq 'prospect_main' ) { push @header, 'Prospect'; - $select .= "$coalesce contact.first||' '||contact.last $as"; - $addl_from = ' LEFT JOIN prospect_main USING ( prospectnum )'; - $extra_sql = ' prospectnum IS NOT NULL '; + push @select, + "COALESCE( prospect_main.company, contact.first||' '||contact.last $as", + map "prospect_contact.$_", qw( prospectnum classnum comment ); + $addl_from = + ' LEFT JOIN prospect_contact USING ( contactnum ) '. + ' LEFT JOIN prospect_main ON ( prospect_contact.prospectnum = prospect_main.prospectnum )'; + $extra_sql = ' prospect_contact.prospectnum IS NOT NULL '; $company_link = [ $p.'view/prospect_main.html?', 'prospectnum' ]; } else { die "don't know how to report on contacts linked to specified table"; @@ -62,6 +86,9 @@ if ( $cgi->param('link') ) { push @header, 'Self-service'; push @fields, 'selfservice_access'; +push @header, 'Comment'; +push @fields, 'comment'; + $extra_sql = (keys(%hash) ? ' AND ' : ' WHERE '). $extra_sql if $extra_sql; diff --git a/httemplate/search/cust_msg.html b/httemplate/search/cust_msg.html index 486c7b09c..d5b865c3b 100644 --- a/httemplate/search/cust_msg.html +++ b/httemplate/search/cust_msg.html @@ -47,7 +47,7 @@ ], 'html_init' => $html_init, 'really_disable_download' => 1, - @_ + @_ #why? &> <%init> #hmm... @@ -71,7 +71,7 @@ if ( $cgi->param('msgtype') =~ /^(\w+)$/ ) { push @where, "msgtype = '$1'"; } if ( $cgi->param('custnum') =~ /^(\d+)$/ ) { - push @where, "custnum = $1"; + push @where, "cust_msg.custnum = $1"; } my ($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, ''); push @where, "(_date >= $beginning AND _date <= $ending)"; diff --git a/httemplate/search/prospect_main.html b/httemplate/search/prospect_main.html index 4798f58f2..241918b98 100644 --- a/httemplate/search/prospect_main.html +++ b/httemplate/search/prospect_main.html @@ -12,9 +12,9 @@ sub { my $pm = shift; [ map { - [ { 'data' => $_->line, }, ]; + [ { 'data'=>$_->contact->line, }, ]; } - $pm->contact + $pm->prospect_contact ]; }, ], diff --git a/httemplate/view/cust_main/contacts_new.html b/httemplate/view/cust_main/contacts_new.html index f73483ae1..f0bc0b848 100644 --- a/httemplate/view/cust_main/contacts_new.html +++ b/httemplate/view/cust_main/contacts_new.html @@ -6,26 +6,31 @@ % my $bgcolor1 = '#eeeeee'; % my $bgcolor2 = '#ffffff'; % my $bgcolor = $bgcolor2; +% my $th = '<TH CLASS="grid" ALIGN="left" BGCOLOR="#cccccc">'; <TR> - <TH CLASS="grid" ALIGN="left" BGCOLOR="#cccccc">Type</TH> - <TH CLASS="grid" ALIGN="left" BGCOLOR="#cccccc">Contact</TH> - <TH CLASS="grid" ALIGN="left" BGCOLOR="#cccccc">Email</TH> - <TH CLASS="grid" ALIGN="left" BGCOLOR="#cccccc">Self-service</TH> + <%$th%>Type</TH> + <%$th%>Contact</TH> + <%$th%>Email</TH> + <%$th%>Self-service</TH> % foreach my $phone_type (@phone_type) { - <TH CLASS="grid" ALIGN="left" BGCOLOR="#cccccc"><% $phone_type->typename |h %> phone</TD> + <%$th%><% $phone_type->typename |h %></TH> % } + <%$th%>Comment</TH> </TR> -% foreach my $contact ( @contacts ) { +% foreach my $cust_contact ( @cust_contacts ) { +% my $contact = $cust_contact->contact; +% my $td = qq(<TD CLASS="grid" BGCOLOR="$bgcolor">); + <TR> - <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $contact->contact_classname |h %></TD> - <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $contact->line |h %></TD> + <%$td%><% $cust_contact->contact_classname |h %></TD> + <%$td%><% $contact->line |h %></TD> % my @contact_email = $contact->contact_email; - <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% join(', ', map $_->emailaddress, @contact_email) %></TD> + <%$td%><% join(', ', map $_->emailaddress, @contact_email) %></TD> - <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> -% if ( $contact->selfservice_access ) { + <%$td%> +% if ( $cust_contact->selfservice_access ) { Enabled %# <FONT SIZE="-1"><A HREF="XXX">disable</A> %# <A HREF="XXX">re-email</A></FONT> @@ -41,9 +46,11 @@ % 'contactnum' => $contact->contactnum, % 'phonetypenum' => $phone_type->phonetypenum, % }); - <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $contact_phone ? $contact_phone->phonenum_pretty : '' |h %></TD> + <%$td%><% $contact_phone ? $contact_phone->phonenum_pretty : '' |h %></TD> % } + <%$td%><% $cust_contact->comment |h %></TD> + </TR> % if ( $bgcolor eq $bgcolor1 ) { @@ -63,6 +70,6 @@ my @phone_type = qsearch({table=>'phone_type', order_by=>'weight'}); my( $cust_main ) = @_; #my $conf = new FS::Conf; -my @contacts = $cust_main->cust_contact; +my @cust_contacts = $cust_main->cust_contact; </%init> diff --git a/httemplate/view/prospect_main.html b/httemplate/view/prospect_main.html index 66abffcdd..a1f14a374 100644 --- a/httemplate/view/prospect_main.html +++ b/httemplate/view/prospect_main.html @@ -39,9 +39,10 @@ </TR> % } -% foreach my $contact ( $prospect_main->contact ) { +% foreach my $prospect_contact ( $prospect_main->prospect_contact ) { +% my $contact = $prospect_contact->contact; <TR> - <TD ALIGN="right"><% $contact->contact_classname %> Contact</TD> + <TD ALIGN="right"><% $prospect_contact->contact_classname %> Contact</TD> <TD BGCOLOR="#FFFFFF"><% $contact->line %></TD> </TR> %} |