From a69299c596de60f4b26db7431165f7f3ffe928e2 Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Wed, 14 Mar 2012 13:44:00 -0700 Subject: [PATCH] svc_hardware MAC address input format, #16266 --- FS/FS/svc_hardware.pm | 2 +- httemplate/docs/credits.html | 1 + httemplate/docs/license.html | 5 + httemplate/edit/svc_broadband.cgi | 3 +- httemplate/edit/svc_hardware.cgi | 3 +- httemplate/elements/masked_input_1.1.js | 195 +++++++++++++++++++++++++++++ httemplate/elements/tr-input-mac_addr.html | 11 ++ httemplate/elements/tr-input-mask.html | 41 ++++++ httemplate/view/svc_broadband.cgi | 7 +- httemplate/view/svc_hardware.cgi | 20 ++- 10 files changed, 283 insertions(+), 5 deletions(-) create mode 100644 httemplate/elements/masked_input_1.1.js create mode 100644 httemplate/elements/tr-input-mac_addr.html create mode 100644 httemplate/elements/tr-input-mask.html diff --git a/FS/FS/svc_hardware.pm b/FS/FS/svc_hardware.pm index b4eb8ccfa..22e627538 100644 --- a/FS/FS/svc_hardware.pm +++ b/FS/FS/svc_hardware.pm @@ -164,7 +164,7 @@ sub check { if ( $conf->exists('svc_hardware-check_mac_addr') ) { $hw_addr = uc($hw_addr); $hw_addr =~ /^[0-9A-F]{12}$/ - or return "Illegal (MAC address) ".$self->getfield('hw_addr'); + or return "Illegal (MAC address) '".$self->getfield('hw_addr')."'"; } $self->setfield('hw_addr', $hw_addr); diff --git a/httemplate/docs/credits.html b/httemplate/docs/credits.html index 7b3b0b93a..9bb1decea 100644 --- a/httemplate/docs/credits.html +++ b/httemplate/docs/credits.html @@ -61,6 +61,7 @@ Joe Camadine
Chris Cappuccio
Rebecca Cardennis
Shane Chrisp
+Kendall Conrad
Luke Crawford
Brad Dameron
Dave Denney
diff --git a/httemplate/docs/license.html b/httemplate/docs/license.html index e0e40b76e..fab8cd09f 100644 --- a/httemplate/docs/license.html +++ b/httemplate/docs/license.html @@ -112,6 +112,11 @@ terms as Perl (GPL/Artistic). Contains code derived from HTML::GoogleMapsV3 by David Peters, licensed under the same terms as Perl (GPL/Artistic). +

+Contains the Masked Input JavaScript library by Kendall Conrad, licensed under +a Creative Commons +Attribution-ShareAlike 3.0 United States license. +

diff --git a/httemplate/edit/svc_broadband.cgi b/httemplate/edit/svc_broadband.cgi index b07c725ae..b266928a1 100644 --- a/httemplate/edit/svc_broadband.cgi +++ b/httemplate/edit/svc_broadband.cgi @@ -103,7 +103,8 @@ my @fields = ( qw( description speed_down speed_up ), { field=>'sectornum', type=>'select-tower_sector', }, { field=>'routernum', type=>'select-router_block_ip' }, - qw( mac_addr latitude longitude altitude vlan_profile + { field=>'mac_addr' , type=>'input-mac_addr' }, + qw( latitude longitude altitude vlan_profile performance_profile authkey plan_id ) ); diff --git a/httemplate/edit/svc_hardware.cgi b/httemplate/edit/svc_hardware.cgi index dcf83de37..d9cd4cd66 100644 --- a/httemplate/edit/svc_hardware.cgi +++ b/httemplate/edit/svc_hardware.cgi @@ -26,7 +26,8 @@ my @fields = ( }, { field => 'hw_addr', - type => 'text', + type => $conf->exists('svc_hardware-check_mac_addr') ? + 'input-mac_addr' : 'text', label => 'Hardware address', }, { diff --git a/httemplate/elements/masked_input_1.1.js b/httemplate/elements/masked_input_1.1.js new file mode 100644 index 000000000..05efa779b --- /dev/null +++ b/httemplate/elements/masked_input_1.1.js @@ -0,0 +1,195 @@ +/*********************************************************************** + 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/tr-input-mac_addr.html b/httemplate/elements/tr-input-mac_addr.html new file mode 100644 index 000000000..d768d4e71 --- /dev/null +++ b/httemplate/elements/tr-input-mac_addr.html @@ -0,0 +1,11 @@ +<& /elements/tr-input-mask.html, + format => '__:__:__:__:__:__', + allowed => '0123456789ABCDEFabcdef', + %opt, +&> +<%init> +my %opt = @_; +my $value = length($opt{curr_value}) ? $opt{curr_value} : $opt{value}; +$value =~ s/\W//g; +$opt{curr_value} = join(':', $value =~ /../g); + diff --git a/httemplate/elements/tr-input-mask.html b/httemplate/elements/tr-input-mask.html new file mode 100644 index 000000000..33725b9a5 --- /dev/null +++ b/httemplate/elements/tr-input-mask.html @@ -0,0 +1,41 @@ +% if ( !$init ) { + +% $init++; +% } +<& /elements/tr-input-text.html, id => $id, @_ &> + +<%shared> +my $init = 0; + +<%init> +my %opt = @_; +# must have a DOM id +my $id = $opt{id} || sprintf('input%04d',int(rand(10000))); +my $value = length($opt{curr_value}) ? $opt{curr_value} : $opt{value} || ''; + +<%doc> +Set up a text input field with input masking. + +<& /elements/tr-input-mask.html, + format => '____-__-__', + #typeon => '_YMDhms', # which characters in the format represent blanks + #allowed => '0123456789', # characters allowed in the blanks + ... all other options as for tr-input-text.html +&> + +Note that the value sent on form submission will contain the mask +separators, and if value/curr_value is passed, it should also be +formatted to fit the mask. + +Uses masked_input_1.1.js by Kendall Conrad, available under a Creative Commons +Attribution-ShareAlike license. + diff --git a/httemplate/view/svc_broadband.cgi b/httemplate/view/svc_broadband.cgi index af2c575b9..131582f1c 100644 --- a/httemplate/view/svc_broadband.cgi +++ b/httemplate/view/svc_broadband.cgi @@ -31,7 +31,7 @@ my @fields = ( 'speed_up', { field => 'ip_addr', value => \&ip_addr }, { field => 'sectornum', value => \§ornum }, - 'mac_addr', + { field => 'mac_addr', value => \&mac_addr }, #'latitude', #'longitude', { field => 'coordinates', value => \&coordinates }, @@ -66,6 +66,11 @@ sub ip_addr { $out; } +sub mac_addr { + my $svc = shift; + join(':', $svc->mac_addr =~ /../g); +} + sub usergroup { my $svc = shift; my $usergroup = $svc->usergroup; diff --git a/httemplate/view/svc_hardware.cgi b/httemplate/view/svc_hardware.cgi index 1d882352b..725358cad 100644 --- a/httemplate/view/svc_hardware.cgi +++ b/httemplate/view/svc_hardware.cgi @@ -6,6 +6,7 @@ %> <%init> +my $conf = new FS::Conf; my $fields = FS::svc_hardware->table_info->{'fields'}; my %labels = map { $_ => ( ref($fields->{$_}) ? $fields->{$_}{'label'} @@ -24,5 +25,22 @@ my $note = { field => 'note', type => 'text', value => sub { encode_entities($_[0]->note) } }; -my @fields = ($model, qw( serial hw_addr ip_addr smartcard ), $status, $note ); +my $hw_addr ={ field => 'hw_addr', + type => 'text', + value => sub { + my $hw_addr = $_[0]->hw_addr; + $conf->exists('svc_hardware-check_mac_addr') ? + join(':', $hw_addr =~ /../g) : $hw_addr + }, + }; + +my @fields = ( + $model, + 'serial', + $hw_addr, + 'ip_addr', + 'smartcard', + $status, + $note, +); -- 2.11.0