From: Mike Rylander Date: Thu, 12 Feb 2015 00:23:32 +0000 (-0500) Subject: LP#1402797 Global undo/redo stack stack X-Git-Tag: sprint4-merge-nov22~1496 X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=fd2b7b48a17613bbdfd3de240e4fd6bc54dc6e37;p=working%2FEvergreen.git LP#1402797 Global undo/redo stack stack Signed-off-by: Mike Rylander Signed-off-by: Bill Erickson --- diff --git a/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js b/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js index 8f53a5451e..6b517ab1b8 100644 --- a/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js +++ b/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js @@ -25,7 +25,6 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap']) $scope.$parent.$parent.content = replace_with }) }, 0); - console.log('well, replaced it'); $($element).parent().css({display: 'none'}); } } @@ -36,16 +35,17 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap']) .directive("egMarcEditEditable", ['$timeout', '$compile', '$document', function ($timeout, $compile, $document) { return { restrict: 'E', - replace: false, - transclude: true, + replace: true, template: '', scope: { field: '=', + onKeydown: '=', subfield: '=', content: '=', contextItemContainer: '@', + idPath: '=', max: '@', - type: '@' + itype: '@' }, controller : ['$scope', function ( $scope ) { @@ -71,6 +71,10 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap']) if ($scope.context_menu_element) { console.log('Reshowing context menu...'); $($scope.context_menu_element).css({ display: 'block', top: event.pageY, left: event.pageX }); + $('body').on('click.context_menu',function() { + $($scope.context_menu_element).css('display','none'); + $('body').off('click.context_menu'); + }); return false; } @@ -83,26 +87,26 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap']) ''; var tnode = angular.element(tmpl); - console.log('... got element ...'); - $document.find('body').append(tnode); - console.log('... attached to DOM ...'); $(tnode).css({ display: 'block', top: event.pageY, left: event.pageX }); - console.log('... displayed ...'); $scope.context_menu_element = tnode; - console.log('... captured for later ...'); $timeout(function() { var e = $compile(tnode)($scope); - console.log('... compiled: ' + e); }, 0); + + $('body').on('click.context_menu',function() { + $(tnode).css('display','none'); + $('body').off('click.context_menu'); + }); + return false; } @@ -113,7 +117,10 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap']) ], link: function (scope, element, attrs) { + if (scope.onKeydown) element.bind('keydown', scope.onKeydown); + element.bind('change', function (e) { element.size = scope.max || parseInt(scope.content.length * 1.1) }); + if (scope.contextItemContainer && angular.isArray(scope[scope.contextItemContainer])) element.bind('contextmenu', scope.showContext); } @@ -122,71 +129,94 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap']) .directive("egMarcEditSubfield", function () { return { + transclude: true, restrict: 'E', template: ''+ - ''+ - ''+ + ''+ + ''+ '', - scope: { field: "=", subfield: "=" }, + scope: { field: "=", subfield: "=", onKeydown: '=' }, replace: false } }) .directive("egMarcEditInd", function () { return { + transclude: true, restrict: 'E', - template: '', - scope: { ind : '=', field: '=' }, + template: '', + scope: { ind : '=', field: '=', onKeydown: '=', indNumber: '@' }, replace: false, } }) .directive("egMarcEditTag", function () { return { + transclude: true, restrict: 'E', - template: '', - scope: { tag : '=', field: '=' }, + template: '', + scope: { tag : '=', field: '=', onKeydown: '=' }, replace: false } }) .directive("egMarcEditDatafield", function () { return { + transclude: true, restrict: 'E', template: '
'+ - ''+ - ''+ - ''+ - ''+ + ''+ + ''+ + ''+ + ''+ '
', - scope: { field: "=" } + scope: { field: "=", onKeydown: '=' } } }) .directive("egMarcEditControlfield", function () { return { + transclude: true, restrict: 'E', template: '
'+ - ''+ - ''+ + ''+ + ''+ '
', - scope: { field: "=" } + scope: { field: "=", onKeydown: '=' } } }) .directive("egMarcEditLeader", function () { return { + transclude: true, restrict: 'E', template: '
'+ - ''+ - ''+ + ''+ + ''+ '
', controller : ['$scope', function ( $scope ) { $scope.tag = 'LDR'; } ], - scope: { record: "=" } + scope: { record: "=", onKeydown: '=' } } }) @@ -195,31 +225,131 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap']) return { template: '
'+ '
'+ - '
'+ - '
'+ - '
'+ + '
'+ + '
'+ + '
'+ '
'+ ''+ '
'+ '', restrict: 'E', replace: false, - scope: { recordId : '=' }, - controller : ['$scope','egCore', - function ( $scope , egCore ) { + scope: { recordId : '=', maxUndo : '@' }, + controller : ['$timeout','$scope','egCore', + function ( $timeout , $scope , egCore ) { + + $scope.max_undo = $scope.maxUndo || 100; + $scope.record_undo_stack = []; + $scope.record_redo_stack = []; + $scope.in_undo = false; + $scope.in_redo = false; + $scope.record = new MARC.Record(); + + $scope.onKeydown = function (event) { + var event_return = true; + + if (event.which == 89 && event.ctrlKey) { // ctrl+y, redo + event_return = $scope.processRedo(); + } else if (event.which == 90 && event.ctrlKey) { // ctrl+z, undo + event_return = $scope.processUndo(); + } else { // Assumes only marc editor elements have IDs that can trigger this event handler. + $scope.current_event_target = $(event.target).attr('id'); + if ($scope.current_event_target) { + $scope.current_event_target_cursor_pos = + event.target.selectionDirection=='backward' ? + event.target.selectionStart : + event.target.selectionEnd; + } + } + + return event_return; + }; + + function setCaret() { + if ($scope.current_event_target) { + var element = $('#'+$scope.current_event_target).get(0); + element.focus(); + element.setSelectionRange( + $scope.current_event_target_cursor_pos, + $scope.current_event_target_cursor_pos + ); + $scope.current_event_target = null; + } + } function loadRecord() { return egCore.pcrud.retrieve( 'bre', $scope.recordId ).then(function(rec) { + $scope.in_redo = true; $scope.bre = rec; - $scope.record = new MARC.Record(); - $scope.record.fromXmlString( $scope.bre.marc() ); + $scope.record = new MARC.Record({ marcxml : $scope.bre.marc() }); $scope.controlfields = $scope.record.fields.filter(function(f){ return f.isControlfield() }); $scope.datafields = $scope.record.fields.filter(function(f){ return !f.isControlfield() }); - }); + }).then(setCaret); } + $scope.$watch('record.toBreaker()', function (newVal, oldVal) { + if (!$scope.in_undo && !$scope.in_redo && oldVal != newVal) { + $scope.record_undo_stack.push({ + breaker: oldVal, + target: $scope.current_event_target, + pos: $scope.current_event_target_cursor_pos + }); + } + + if ($scope.record_undo_stack.length > $scope.max_undo) + $scope.record_undo_stack.shift(); + + console.log('undo stack is ' + $scope.record_undo_stack.length + ' deep'); + $scope.in_redo = false; + $scope.in_undo = false; + }); + + $scope.processUndo = function () { + if ($scope.record_undo_stack.length) { + $scope.in_undo = true; + + var undo_item = $scope.record_undo_stack.pop(); + $scope.record_redo_stack.push(undo_item); + + $scope.record = new MARC.Record({ marcbreaker : undo_item.breaker }); + $scope.controlfields = $scope.record.fields.filter(function(f){ return f.isControlfield() }); + $scope.datafields = $scope.record.fields.filter(function(f){ return !f.isControlfield() }); + + $scope.current_event_target = undo_item.target; + $scope.current_event_target_cursor_pos = undo_item.pos; + console.log('Undo targeting ' + $scope.current_event_target + ' position ' + $scope.current_event_target_cursor_pos); + + $timeout(function(){$scope.$digest()}).then(setCaret); + return false; + } + + return true; + }; + + $scope.processRedo = function () { + if ($scope.record_redo_stack.length) { + $scope.in_redo = true; + + var redo_item = $scope.record_redo_stack.pop(); + $scope.record_undo_stack.push(redo_item); + + $scope.record = new MARC.Record({ marcbreaker : redo_item.breaker }); + $scope.controlfields = $scope.record.fields.filter(function(f){ return f.isControlfield() }); + $scope.datafields = $scope.record.fields.filter(function(f){ return !f.isControlfield() }); + + $scope.current_event_target = redo_item.target; + $scope.current_event_target_cursor_pos = redo_item.pos; + console.log('Redo targeting ' + $scope.current_event_target + ' position ' + $scope.current_event_target_cursor_pos); + + $timeout(function(){$scope.$digest()}).then(setCaret); + return false; + } + + return true; + }; + $scope.saveRecord = function () { $scope.bre.marc($scope.record.toXmlString()); return egCore.pcrud.update( diff --git a/Open-ILS/web/js/ui/default/staff/marcrecord.js b/Open-ILS/web/js/ui/default/staff/marcrecord.js index 876540a9ec..22e53547cf 100644 --- a/Open-ILS/web/js/ui/default/staff/marcrecord.js +++ b/Open-ILS/web/js/ui/default/staff/marcrecord.js @@ -116,13 +116,11 @@ var MARC = { // this.clone = function () { return dojo.clone(this) } // maybe implement later... this.fromXmlURL = function (url) { - this.ready = false; var me = this; return $.get( // This is a Promise url, function (mxml) { me.fromXmlDocument($('record', mxml)[0]); - me.ready = true; if (me.onLoad) me.onLoad(); }); }, @@ -135,18 +133,21 @@ var MARC = { var me = this; me.leader = $($('leader',mxml)[0]).text() || '00000cam a2200205Ka 4500'; - $('controlfield', mxml).each(function () { + $('controlfield', mxml).each(function (ind) { var cf=$(this); me.fields.push( new MARC.Field({ record : me, tag : cf.attr('tag'), - data : cf.text() + data : cf.text(), + position: ind }) ) }); - $('datafield', mxml).each(function () { + var cfield_count = me.fields.length + 1; + + $('datafield', mxml).each(function (ind) { var df=$(this); me.fields.push( new MARC.Field({ @@ -154,14 +155,16 @@ var MARC = { tag : df.attr('tag'), ind1 : df.attr('ind1'), ind2 : df.attr('ind2'), + position : ind + cfield_count, subfields : $('subfield', df).map( function (i, sf) { - return [[ $(sf).attr('code'), $(sf).text() ]]; + return [[ $(sf).attr('code'), $(sf).text(), i ]]; } ).get() }) ) }); + me.ready = true; }, @@ -216,7 +219,7 @@ var MARC = { } var lines = marctxt.replace(/^=/gm,'').split('\n'); - lines.forEach(function (current_line) { + lines.forEach(function (current_line, ind) { if (current_line.match(/^#/)) { // skip comment lines @@ -228,7 +231,8 @@ var MARC = { new MARC.Field({ record : me, tag : line_tag(current_line), - data : cf_line_data(current_line).replace('\\',' ','g') + data : cf_line_data(current_line).replace('\\',' ','g'), + position: ind }) ); } @@ -248,16 +252,18 @@ var MARC = { tag : line_tag(current_line), ind1 : df_ind1(current_line), ind2 : df_ind2(current_line), - subfields : sf_list.map( function (sf) { + position : ind, + subfields : sf_list.map( function (sf, i) { var sf_data = sf.substring(1); if (me.delimiter == '$') sf_data = sf_data.replace(/\{dollar\}/g, '$'); - return [ sf.substring(0,1), sf_data ]; + return [ sf.substring(0,1), sf_data, i ]; }) }) ); } }); + me.ready = true; return this; }, @@ -446,6 +452,7 @@ var MARC = { return val; } + this.ready = false; this.fields = []; this.delimiter = '\u2021'; this.leader = '00000cam a2200205Ka 4500'; @@ -571,6 +578,7 @@ var MARC = { this.data = ''; // MARC data for a controlfield element this.subfields = []; // list of MARC subfields for a datafield element + this.position = kwargs.position; this.record = kwargs.record; this.tag = kwargs.tag; this.ind1 = kwargs.ind1 || ' ';