Parcourir la source

[grid] Keyboard navigation to Move Up, Down, Close

version-14
Rushabh Mehta il y a 12 ans
Parent
révision
5a6de0a704
6 fichiers modifiés avec 83 ajouts et 601 suppressions
  1. +0
    -555
      public/js/lib/imask.js
  2. +4
    -4
      public/js/lib/jquery/jquery.hotkeys.js
  3. +42
    -12
      public/js/wn/app.js
  4. +0
    -11
      public/js/wn/form/control.js
  5. +29
    -18
      public/js/wn/form/grid.js
  6. +8
    -1
      webnotes/__init__.py

+ 0
- 555
public/js/lib/imask.js Voir le fichier

@@ -1,555 +0,0 @@
(function($){
var $chk = function(obj){
return !!(obj || obj === 0);
};

/**
* Masks an input with a pattern
* @class
* @requires jQuery
* @name jQuery.iMask
* @param {Object} options
* @param {String} options.type (number|fixed)
* @param {String} options.mask Mask using 9,a,x notation
* @param {String} [options.maskEmptyChr=' ']
* @param {String} [options.validNumbers='1234567890']
* @param {String} [options.validAlphas='abcdefghijklmnopqrstuvwxyz']
* @param {String} [options.validAlphaNums='abcdefghijklmnopqrstuvwxyz1234567890']
* @param {Number} [options.groupDigits=3]
* @param {Number} [options.decDigits=2]
* @param {String} [options.currencySymbol]
* @param {String} [options.groupSymbol=',']
* @param {String} [options.decSymbol='.']
* @param {Boolean} [options.showMask=true]
* @param {Boolean} [options.stripMask=false]
* @param {Function} [options.sanity]
* @param {Object} [options.number] Override options for when validating numbers only
* @param {Boolean} [options.number.stripMask=false]
* @param {Boolean} [options.number.showMask=false]
*/
var iMask = function(){
this.initialize.apply(this, arguments);
};

iMask.prototype = {
std_options: {
maskEmptyChr : ' ',

validNumbers : "1234567890",
validAlphas : "abcdefghijklmnopqrstuvwxyz",
validAlphaNums : "abcdefghijklmnopqrstuvwxyz1234567890",

groupDigits : 3,
decDigits : 2,
currencySymbol : '',
groupSymbol : ',',
decSymbol : '.',
showMask : true,
stripMask : false,

lastFocus : 0,

number : {
stripMask : false,
showMask : false
}
},

initialize: function(node, options) {
this.node = node;
this.domNode = node[0];
this.setOptions(options);
var self = this;

if(options.type == "number") this.node.css("text-align", "right");

this.node
.bind( "mousedown click", function(ev){
ev.stopPropagation(); ev.preventDefault(); } )

.bind( "mouseup", function(){ self.onMouseUp .apply(self, arguments); } )
.bind( "keydown", function(){ self.onKeyDown .apply(self, arguments); } )
.bind( "keypress", function(){ self.onKeyPress.apply(self, arguments); } )
.bind( "focus", function(){ self.onFocus .apply(self, arguments); } )
//.bind( "blur", function(){ self.onBlur .apply(self, arguments); } );
},
setOptions: function(options) {
this.options = $.extend({},
this.std_options, this.std_options[options.type] || {}, options);
},

isFixed : function(){ return this.options.type == 'fixed'; },
isNumber : function(){ return this.options.type == 'number'; },

onMouseUp: function( ev ) {
ev.stopPropagation();
ev.preventDefault();

if( this.isFixed() ) {
var p = this.getSelectionStart();
this.setSelection(p, (p + 1));
} else if(this.isNumber() ) {
this.setEnd();
}
},

onKeyDown: function(ev) {
if(ev.ctrlKey || ev.altKey || ev.metaKey) {
return;

} else if(ev.which == 13) { // enter
this.node.blur();

this.submitForm(this.node);

} else if(!(ev.which == 9)) { // tab
if(this.options.type == "fixed") {
ev.preventDefault();

var p = this.getSelectionStart();
switch(ev.which) {
case 8: // Backspace
this.updateSelection( this.options.maskEmptyChr );
this.selectPrevious();
break;
case 36: // Home
this.selectFirst();
break;
case 35: // End
this.selectLast();
break;
case 37: // Left
case 38: // Up
this.selectPrevious();
break;
case 39: // Right
case 40: // Down
this.selectNext();
break;
case 46: // Delete
this.updateSelection( this.options.maskEmptyChr );
this.selectNext();
break;
default:
var chr = this.chrFromEv(ev);
if( this.isViableInput( p, chr ) ) {
this.updateSelection( ev.shiftKey ? chr.toUpperCase() : chr );
this.node.trigger("valid", ev, this.node);
this.selectNext();
} else {
this.node.trigger("invalid", ev, this.node);
}
break;
}
} else if(this.options.type == "number") {
switch(ev.which) {
case 35: // END
case 36: // HOME
case 37: // LEFT
case 38: // UP
case 39: // RIGHT
case 40: // DOWN
break;
case 8: // backspace
case 46: // delete
var self = this;
setTimeout(function(){
self.formatNumber();
}, 1);
break;

default:
ev.preventDefault();

var chr = this.chrFromEv( ev );
if( this.isViableInput( p, chr ) ) {
var range = new Range( this )
, val = this.sanityTest( range.replaceWith( chr ) );

if(val !== false){
this.updateSelection( chr );
this.formatNumber();
}
this.node.trigger( "valid", ev, this.node );
} else {
this.node.trigger( "invalid", ev, this.node );
}
break;
}
}
}
},

allowKeys : {
8 : 1 // backspace
, 9 : 1 // tab
, 13 : 1 // enter
, 35 : 1 // end
, 36 : 1 // home
, 37 : 1 // left
, 38 : 1 // up
, 39 : 1 // right
, 40 : 1 // down
, 46 : 1 // delete
},

onKeyPress: function(ev) {
var key = ev.which || ev.keyCode;

if(
!( this.allowKeys[ key ] )
&& !(ev.ctrlKey || ev.altKey || ev.metaKey)
) {
ev.preventDefault();
ev.stopPropagation();
}
},

onFocus: function(ev) {
ev.stopPropagation();
ev.preventDefault();

this.options.showMask && (this.domNode.value = this.wearMask(this.domNode.value));
this.sanityTest( this.domNode.value );

var self = this;

setTimeout( function(){
self[ self.options.type === "fixed" ? 'selectFirst' : 'setEnd' ]();
}, 1 );
},

onBlur: function(ev) {
// ev.stopPropagation();
// ev.preventDefault();
//
// if(this.options.stripMask)
// this.domNode.value = this.stripMask();
},

selectAll: function() {
this.setSelection(0, this.domNode.value.length);
},

selectFirst: function() {
for(var i = 0, len = this.options.mask.length; i < len; i++) {
if(this.isInputPosition(i)) {
this.setSelection(i, (i + 1));
return;
}
}
},

selectLast: function() {
for(var i = (this.options.mask.length - 1); i >= 0; i--) {
if(this.isInputPosition(i)) {
this.setSelection(i, (i + 1));
return;
}
}
},

selectPrevious: function(p) {
if( !$chk(p) ){ p = this.getSelectionStart(); }

if(p <= 0) {
this.selectFirst();
} else {
if(this.isInputPosition(p - 1)) {
this.setSelection(p - 1, p);
} else {
this.selectPrevious(p - 1);
}
}
},

selectNext: function(p) {
if( !$chk(p) ){ p = this.getSelectionEnd(); }

if( this.isNumber() ){
this.setSelection( p+1, p+1 );
return;
}

if( p >= this.options.mask.length) {
this.selectLast();
} else {
if(this.isInputPosition(p)) {
this.setSelection(p, (p + 1));
} else {
this.selectNext(p + 1);
}
}
},

setSelection: function( a, b ) {
a = a.valueOf();
if( !b && a.splice ){
b = a[1];
a = a[0];
}

if(this.domNode.setSelectionRange) {
this.domNode.focus();
this.domNode.setSelectionRange(a, b);
} else if(this.domNode.createTextRange) {
var r = this.domNode.createTextRange();
r.collapse();
r.moveStart("character", a);
r.moveEnd("character", (b - a));
r.select();
}
},

updateSelection: function( chr ) {
var value = this.domNode.value
, range = new Range( this )
, output = range.replaceWith( chr );

this.domNode.value = output;
if( range[0] === range[1] ){
this.setSelection( range[0] + 1, range[0] + 1 );
}else{
this.setSelection( range );
}
},

setEnd: function() {
var len = this.domNode.value.length;
this.setSelection(len, len);
},

getSelectionRange : function(){
return [ this.getSelectionStart(), this.getSelectionEnd() ];
},

getSelectionStart: function() {
var p = 0,
n = this.domNode.selectionStart;

if( n ) {
if( typeof( n ) == "number" ){
p = n;
}
} else if( document.selection ){
var r = document.selection.createRange().duplicate();
r.moveEnd( "character", this.domNode.value.length );
p = this.domNode.value.lastIndexOf( r.text );
if( r.text == "" ){
p = this.domNode.value.length;
}
}
return p;
},

getSelectionEnd: function() {
var p = 0,
n = this.domNode.selectionEnd;

if( n ) {
if( typeof( n ) == "number"){
p = n;
}
} else if( document.selection ){
var r = document.selection.createRange().duplicate();
r.moveStart( "character", -this.domNode.value.length );
p = r.text.length;
}
return p;
},

isInputPosition: function(p) {
var mask = this.options.mask.toLowerCase();
var chr = mask.charAt(p);
return !!~"9ax".indexOf(chr);
},

sanityTest: function( str, p ){
var sanity = this.options.sanity;

if(sanity instanceof RegExp){
return sanity.test(str);
}else if($.isFunction(sanity)){
var ret = sanity(str, p);
if(typeof(ret) == 'boolean'){
return ret;
}else if(typeof(ret) != 'undefined'){
if( this.isFixed() ){
var p = this.getSelectionStart();
this.domNode.value = this.wearMask( ret );
this.setSelection( p, p+1 );
this.selectNext();
}else if( this.isNumber() ){
var range = new Range( this );
this.domNode.value = ret;
this.setSelection( range );
this.formatNumber();
}
return false;
}
}
},

isViableInput: function() {
return this[ this.isFixed() ? 'isViableFixedInput' : 'isViableNumericInput' ].apply( this, arguments );
},

isViableFixedInput : function( p, chr ){
var mask = this.options.mask.toLowerCase();
var chMask = mask.charAt(p);

var val = this.domNode.value.split('');
val.splice( p, 1, chr );
val = val.join('');

var ret = this.sanityTest( val, p );
if(typeof(ret) == 'boolean'){ return ret; }

if(({
'9' : this.options.validNumbers,
'a' : this.options.validAlphas,
'x' : this.options.validAlphaNums
}[chMask] || '').indexOf(chr) >= 0){
return true;
}

return false;
},

isViableNumericInput : function( p, chr ){
return !!~this.options.validNumbers.indexOf( chr );
},

wearMask: function(str) {
var mask = this.options.mask.toLowerCase()
, output = ""
, chrSets = {
'9' : 'validNumbers'
, 'a' : 'validAlphas'
, 'x' : 'validAlphaNums'
};

for(var i = 0, u = 0, len = mask.length; i < len; i++) {
switch(mask.charAt(i)) {
case '9':
case 'a':
case 'x':
output +=
((this.options[ chrSets[ mask.charAt(i) ] ].indexOf( str.charAt(u).toLowerCase() ) >= 0) && ( str.charAt(u) != ""))
? str.charAt( u++ )
: this.options.maskEmptyChr;
break;

default:
output += mask.charAt(i);
if( str.charAt(u) == mask.charAt(i) ){
u++;
}

break;
}
}
return output;
},

stripMask: function() {
var value = this.domNode.value;
if("" == value) return "";
var output = "";

if( this.isFixed() ) {
for(var i = 0, len = value.length; i < len; i++) {
if((value.charAt(i) != this.options.maskEmptyChr) && (this.isInputPosition(i)))
{output += value.charAt(i);}
}
} else if( this.isNumber() ) {
for(var i = 0, len = value.length; i < len; i++) {
if(this.options.validNumbers.indexOf(value.charAt(i)) >= 0)
{output += value.charAt(i);}
}
}

return output;
},

chrFromEv: function(ev) {
var chr = '', key = ev.which;

if(key >= 96 && key <= 105){ key -= 48; } // shift number-pad numbers to corresponding character codes
chr = String.fromCharCode(key).toLowerCase(); // key pressed as a lowercase string
return chr;
},

formatNumber: function() {
// stripLeadingZeros
var olen = this.domNode.value.length
, str2 = this.stripMask()
, str1 = str2.replace( /^0+/, '' )
, range = new Range(this);

// wearLeadingZeros

str2 = str1;
str1 = "";
for(var len = str2.length, i = this.options.decDigits; len <= i; len++) {
str1 += "0";
}
str1 += str2;

// decimalSymbol
str2 = str1.substr(str1.length - this.options.decDigits)
str1 = str1.substring(0, (str1.length - this.options.decDigits))

// groupSymbols
var re = new RegExp("(\\d+)(\\d{"+ this.options.groupDigits +"})");
while(re.test(str1)) {
str1 = str1.replace(re, "$1"+ this.options.groupSymbol +"$2");
}

this.domNode.value = this.options.currencySymbol + str1 + this.options.decSymbol + str2;
this.setSelection( range );
},

getObjForm: function() {
return this.node.getClosest('form');
},

submitForm: function() {
//var form = this.getObjForm();
//form.trigger('submit');
}
};

function Range( obj ){
this.range = obj.getSelectionRange();
this.len = obj.domNode.value.length
this.obj = obj;

this['0'] = this.range[0];
this['1'] = this.range[1];
}
Range.prototype = {
valueOf : function(){
var len = this.len - this.obj.domNode.value.length;
return [ this.range[0] - len, this.range[1] - len ];
},
replaceWith : function( str ){
var val = this.obj.domNode.value
, range = this.valueOf();

return val.substr( 0, range[0] ) + str + val.substr( range[1] );
}
};

$.fn.iMask = function(options){
this.each(function(){
if(this._imask) {
// don't re-initialize, just reset the options dynamically
this._imask.setOptions(options);
} else {
this._imask = new iMask($(this), options);
}
});
};
})(jQuery);

+ 4
- 4
public/js/lib/jquery/jquery.hotkeys.js Voir le fichier

@@ -44,10 +44,10 @@
handleObj.handler = function( event ) {
// Don't fire in text-accepting inputs that we didn't directly bind to
if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) ||
jQuery.inArray(event.target.type, textAcceptingInputTypes) > -1 ) ) {
return;
}
// if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) ||
// jQuery.inArray(event.target.type, textAcceptingInputTypes) > -1 ) ) {
// return;
// }
// Keypress represents characters, not special keys
var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ],


+ 42
- 12
public/js/wn/app.js Voir le fichier

@@ -196,19 +196,49 @@ wn.Application = Class.extend({
},
setup_keyboard_shortcuts: function() {
$(document).keydown("meta+g ctrl+g", function(e) {
wn.ui.toolbar.search.show();
return false;
});
$(document)
.keydown("meta+g ctrl+g", function(e) {
wn.ui.toolbar.search.show();
return false;
})
.keydown("meta+s ctrl+s", function(e) {
if(cur_frm) {
cur_frm.save_or_update();
} else if(wn.container.page.save_action) {
wn.container.page.save_action();
}
return false;
})
.keydown("esc", function(e) {
var open_row = $(".grid-row-open");
if(open_row.length) {
var grid_row = open_row.data("grid_row");
grid_row.toggle_view(false);
}
})
.keydown("ctrl+down meta+down", function(e) {
var open_row = $(".grid-row-open");
if(open_row.length) {
var grid_row = open_row.data("grid_row");
grid_row.toggle_view(false, function() { grid_row.open_next() });
}
})
.keydown("ctrl+up meta+up", function(e) {
var open_row = $(".grid-row-open");
if(open_row.length) {
var grid_row = open_row.data("grid_row");
grid_row.toggle_view(false, function() { grid_row.open_prev() });
}
})
.keydown("ctrl+n meta+n", function(e) {
var open_row = $(".grid-row-open");
if(open_row.length) {
var grid_row = open_row.data("grid_row");
grid_row.toggle_view(false, function() { grid_row.grid.add_new_row(grid_row.doc.idx, null, true); });
}
return false;
})

$(document).keydown("meta+s ctrl+s", function(e) {
if(cur_frm) {
cur_frm.save_or_update();
} else if(wn.container.page.save_action) {
wn.container.page.save_action();
}
return false;
});
},
run_custom_startup_code: function() {


+ 0
- 11
public/js/wn/form/control.js Voir le fichier

@@ -204,15 +204,6 @@ wn.ui.form.ControlInput = wn.ui.form.Control.extend({
me.doctype && me.docname && me.get_value
&& me.parse_validate_and_set_in_model(me.get_value()); } );
},
bind_save_event: function() {
if(this.frm && this.$input) {
var me = this;
this.$input.keydown("ctrl+s meta+s", function(e) {
me.frm.save_or_update();
return false;
})
}
},
set_label: function(label) {
if(label) this.df.label = label;
@@ -262,7 +253,6 @@ wn.ui.form.ControlData = wn.ui.form.ControlInput.extend({
this.input = this.$input.get(0);
this.has_input = true;
this.bind_change_event();
this.bind_save_event();
},
set_input_attributes: function() {
this.$input
@@ -630,7 +620,6 @@ wn.ui.form.ControlLink = wn.ui.form.ControlData.extend({
me.parse_validate_and_set_in_model(value);
}
}});
this.bind_save_event();
this.setup_buttons();
this.setup_autocomplete();
},


+ 29
- 18
public/js/wn/form/grid.js Voir le fichier

@@ -196,11 +196,10 @@ wn.ui.form.GridRow = Class.extend({
},
remove: function() {
var me = this;
me.wrapper.fadeOut(function() {
wn.model.clear_doc(me.doc.doctype, me.doc.name);
me.frm.dirty();
me.grid.refresh();
});
me.wrapper.toggle(false);
wn.model.clear_doc(me.doc.doctype, me.doc.name);
me.frm.dirty();
me.grid.refresh();
},
insert: function(show) {
var idx = this.doc.idx;
@@ -343,16 +342,29 @@ wn.ui.form.GridRow = Class.extend({
this.show && this.render_form()
this.show && this.row.toggle(false);

this.form_panel.slideToggle(this.show, function() {
if(me.show) {
if(me.frm.doc.docstatus===0)
me.form_area.find(":input:first").focus();
} else {
me.row.toggle(true);
me.make_static_display();
}
callback && callback();
});
this.form_panel.toggle(this.show);
if(me.show) {
if(me.frm.doc.docstatus===0)
me.form_area.find(":input:first").focus();
} else {
me.row.toggle(true);
me.make_static_display();
}
callback && callback();
return this;
},
open_prev: function() {
if(this.grid.grid_rows[this.doc.idx-2]) {
this.grid.grid_rows[this.doc.idx-2].toggle_view(true);
}
},
open_next: function() {
if(this.grid.grid_rows[this.doc.idx]) {
this.grid.grid_rows[this.doc.idx].toggle_view(true);
} else {
this.grid.add_new_row(null, null, true);
}
},
toggle_add_delete_button_display: function($parent) {
$parent.find(".grid-delete-row, .grid-insert-row")
@@ -407,9 +419,7 @@ wn.ui.form.GridRow = Class.extend({
cnt++;
}
});
this.wrapper.find(".footer-toolbar").toggle(me.fields.length > 6);
this.toggle_add_delete_button_display(this.wrapper.find(".panel:first"));
this.grid.open_grid_row = this;
@@ -434,6 +444,7 @@ wn.ui.form.GridRow = Class.extend({
</div>\
<div class="form-area"></div>\
<div class="toolbar footer-toolbar" style="height: 36px;">\
<span class="text-muted">Move Up: Ctrl+<i class="icon-arrow-up"></i>, Move Down: Ctrl+<i class="icon-arrow-down"></i>, Close: Esc</span>\
<button class="btn btn-success pull-right grid-toggle-row" \
title="'+wn._("Close")+'"\
style="margin-left: 7px;">\


+ 8
- 1
webnotes/__init__.py Voir le fichier

@@ -60,6 +60,7 @@ error_log = []
debug_log = []
message_log = []
mute_emails = False
mute_messages = False
test_objects = {}
request_method = None
print_messages = False
@@ -110,6 +111,8 @@ def log(msg):
debug_log.append(cstr(msg))

def msgprint(msg, small=0, raise_exception=0, as_table=False):
if mute_messages:
return
from utils import cstr
if as_table and type(msg) in (list, tuple):
msg = '<table border="1px" style="border-collapse: collapse" cellpadding="2px">' + ''.join(['<tr>'+''.join(['<td>%s</td>' % c for c in r])+'</tr>' for r in msg]) + '</table>'
@@ -314,7 +317,11 @@ def doc(doctype=None, name=None, fielddata=None):
def new_doc(doctype, parent_doc=None, parentfield=None):
from webnotes.model.create_new import get_new_doc
return get_new_doc(doctype, parent_doc, parentfield)

def new_bean(doctype):
from webnotes.model.create_new import get_new_doc
return bean([get_new_doc(doctype)])

def doclist(lst=None):
from webnotes.model.doclist import DocList
return DocList(lst)


Chargement…
Annuler
Enregistrer