From 54b264b6a132ad1b7677fe2f7ad7d587f228b357 Mon Sep 17 00:00:00 2001 From: Giulio Cesare Solaroli Date: Thu, 4 Sep 2014 18:58:56 +0200 Subject: [PATCH] Implemented field manual ordering through drag&drop --- .../Clipperz/PM/DataModel/Record.Version.js | 3 +- .../delta/js/Clipperz/PM/DataModel/Record.js | 139 ++++++++- .../Clipperz/PM/UI/Components/Cards/Edit.js | 246 ++++++++++++++- frontend/delta/scss/clipperz.scss | 2 + frontend/delta/scss/core/layout.scss | 3 + frontend/delta/scss/style/card.scss | 12 + .../Clipperz/PM/DataModel/Record.test.js | 280 +++++++++++++++++- .../tests/Clipperz/PM/DataModel/User.test.js | 47 ++- 8 files changed, 707 insertions(+), 25 deletions(-) diff --git a/frontend/delta/js/Clipperz/PM/DataModel/Record.Version.js b/frontend/delta/js/Clipperz/PM/DataModel/Record.Version.js index ad1bce0..b7de8c8 100644 --- a/frontend/delta/js/Clipperz/PM/DataModel/Record.Version.js +++ b/frontend/delta/js/Clipperz/PM/DataModel/Record.Version.js @@ -52,6 +52,7 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record.Version, Clipperz.PM.DataModel 'hasPendingChanges': function () { var deferredResult; +console.log("Record.Version.hasPendingChanges"); deferredResult = new Clipperz.Async.Deferred("Clipperz.PM.DataModel.Record.Version.hasPendingChanges", {trace:false}); deferredResult.addCallback(MochiKit.Base.bind(Clipperz.PM.DataModel.Record.Version.superclass.hasPendingChanges, this)); deferredResult.callback(); @@ -111,8 +112,6 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record.Version, Clipperz.PM.DataModel } } - - return result; }, diff --git a/frontend/delta/js/Clipperz/PM/DataModel/Record.js b/frontend/delta/js/Clipperz/PM/DataModel/Record.js index 765133f..209de7a 100644 --- a/frontend/delta/js/Clipperz/PM/DataModel/Record.js +++ b/frontend/delta/js/Clipperz/PM/DataModel/Record.js @@ -769,13 +769,11 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record, Clipperz.PM.DataModel.Encrypt }, //========================================================================= - +/* 'hasPendingChanges': function () { var deferredResult; - /*if (this.isBrandNew()) { - deferredResult = MochiKit.Async.succeed(true); - } else*/ if (this.hasInitiatedObjectDataStore()) { + if (this.hasInitiatedObjectDataStore()) { deferredResult = new Clipperz.Async.Deferred("Clipperz.PM.DataModel.Record.hasPendingChanges", {trace:false}); deferredResult.collectResults({ 'super': MochiKit.Base.bind(Clipperz.PM.DataModel.Record.superclass.hasPendingChanges, this), @@ -795,6 +793,7 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record, Clipperz.PM.DataModel.Encrypt // } ] }); +deferredResult.addCallback(function (aValue) { console.log("Record.hasPendingChanges", aValue); return aValue; }); deferredResult.addCallback(MochiKit.Base.values); deferredResult.addCallback(MochiKit.Base.bind(function(someValues) { var result; @@ -823,6 +822,67 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record, Clipperz.PM.DataModel.Encrypt return deferredResult; }, +*/ + + 'hasPendingChanges': function () { + var deferredResult; +// var recordReference = this.reference(); + var self = this; + + deferredResult = new Clipperz.Async.Deferred("Clipperz.PM.DataModel.Record.hasPendingChanges", {trace:false}); + deferredResult.collectResults({ + 'super': [ + MochiKit.Base.bind(Clipperz.PM.DataModel.Record.superclass.hasPendingChanges, this), + +// MochiKit.Base.method(this, 'hasInitiatedObjectDataStore'), +// Clipperz.Async.deferredIf("Record.hasPendingChanges - hasInitiatedObjectDataStore", [ +// MochiKit.Base.bind(Clipperz.PM.DataModel.Record.superclass.hasPendingChanges, this), +// ], [ +// MochiKit.Base.partial(MochiKit.Async.succeed, false), +// ]), + ], + 'currentVersion': [ +// MochiKit.Base.method(this, 'invokeCurrentRecordVersionMethod', 'hasPendingChanges') + MochiKit.Base.method(this, 'hasInitiatedObjectDataStore'), + Clipperz.Async.deferredIf("Record.hasPendingChanges - hasInitiatedObjectDataStore", [ + MochiKit.Base.method(this, 'invokeCurrentRecordVersionMethod', 'hasPendingChanges') + ], [ + MochiKit.Base.partial(MochiKit.Async.succeed, false), + ]), + ], + 'directLogins': [ + MochiKit.Base.method(this, 'directLogins'), + MochiKit.Base.values, + MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.methodcaller('hasPendingChanges')), + Clipperz.Async.collectAll, + Clipperz.Async.or + ] + }); +//deferredResult.addCallback(function (someValues) { +// if (recordReference == 'd620764a656bfd4e1d3758500d5db72e460a0cf729d56ed1a7755b5725c50045') { +// console.log("Record.hasPendingChanges VALUES", someValues); +// } +// return someValues; +//}) + deferredResult.addCallback(MochiKit.Base.values); + deferredResult.addCallback(MochiKit.Base.bind(function(someValues) { + var result; + result = MochiKit.Iter.some(someValues, MochiKit.Base.operator.identity); +/* + if ((result == false) && (this.isBrandNew() == false)) { +console.log("TRANSIENT STATE", this.transientState()); +console.log("TRANSIENT STATE - hasPendingChanges", this.transientState().getValue('hasPendingChanges.indexData')); + result = MochiKit.Iter.some(MochiKit.Base.values(this.transientState().getValue('hasPendingChanges.indexData')), MochiKit.Base.operator.identity); + } +console.log("Record.hasPendingChanges RESULT", result); +*/ + return result; + }, this)); + + deferredResult.callback(); + + return deferredResult; + }, //------------------------------------------------------------------------- @@ -872,15 +932,21 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record, Clipperz.PM.DataModel.Encrypt 'revertChanges': function () { var deferredResult; - + var recordReference = this.reference(); + if (this.isBrandNew() == false) { +/* deferredResult = new Clipperz.Async.Deferred("Clipperz.PM.DataModel.Record.revertChanges", {trace:false}); deferredResult.addMethod(this, 'hasPendingChanges'); +deferredResult.addCallback(function (aValue) { + if (recordReference == 'd620764a656bfd4e1d3758500d5db72e460a0cf729d56ed1a7755b5725c50045') { + console.log("Record.revertChanges - hasPendingChanges", aValue); + } +// return aValue; + return true; +}); deferredResult.addIf([ -// MochiKit.Base.method(this, 'getCurrentRecordVersion'), -// MochiKit.Base.methodcaller('revertChanges'), - MochiKit.Base.method(this,'invokeCurrentRecordVersionMethod', 'revertChanges'), - + MochiKit.Base.method(this, 'invokeCurrentRecordVersionMethod', 'revertChanges'), MochiKit.Base.method(this, 'directLogins'), MochiKit.Base.values, MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.methodcaller('revertChanges')), @@ -890,6 +956,15 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record, Clipperz.PM.DataModel.Encrypt MochiKit.Async.succeed ]); deferredResult.callback(); +*/ + deferredResult = Clipperz.Async.callbacks("Clipperz.PM.DataModel.Record.revertChanges", [ + MochiKit.Base.method(this, 'invokeCurrentRecordVersionMethod', 'revertChanges'), + MochiKit.Base.method(this, 'directLogins'), + MochiKit.Base.values, + MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.methodcaller('revertChanges')), + + MochiKit.Base.bind(Clipperz.PM.DataModel.Record.superclass.revertChanges, this) + ], {trace:false}); } else { // this.deleteAllCleanTextData(); deferredResult = MochiKit.Async.succeed(); @@ -1005,6 +1080,45 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record, Clipperz.PM.DataModel.Encrypt ]) }, + //------------------------------------------------------------------------- + + 'moveFieldToPosition': function (aFieldReference, aPosition) { + var deferredResult; + var currentFieldValues; + var fromPosition; + + deferredResult = new Clipperz.Async.Deferred("Clipperz.PM.DataModel.Record.moveFieldToPosition", {trace:false}); + deferredResult.addMethod(this, 'getFieldsValues'); + deferredResult.addCallback(function (someValues) { + fromPosition = MochiKit.Base.keys(someValues).indexOf(aFieldReference); + + return ((fromPosition != -1) && (fromPosition!= aPosition)); + }); + deferredResult.addIf([ + MochiKit.Base.method(this, 'getFieldsValues'), + function (someValues) { currentFieldValues = Clipperz.Base.deepClone(someValues); return currentFieldValues}, + MochiKit.Base.method(this, 'fields'), MochiKit.Base.values, + MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.method(this, 'removeField')), + Clipperz.Async.collectAll, + + function () { + var currentFieldKeys = MochiKit.Base.keys(currentFieldValues); + currentFieldKeys.splice(aPosition, 0, currentFieldKeys.splice(fromPosition, 1)[0]); + return currentFieldKeys; + }, +//function (aValue) { console.log("Sorted Keys", aValue); return aValue; }, + MochiKit.Base.partial(MochiKit.Base.map, function (aReference) { return currentFieldValues[aReference]; }), +function (aValue) { console.log("Sorted Field values", aValue); return aValue; }, + MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.method(this, 'addField')), + Clipperz.Async.collectAll, + ], [ + MochiKit.Async.succeed + ]); + deferredResult.callback(); + + return deferredResult; + }, + //========================================================================= 'setUpWithRecord': function (aRecord) { @@ -1025,9 +1139,10 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record, Clipperz.PM.DataModel.Encrypt MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.method(this, 'addField')), Clipperz.Async.collectAll, -// MochiKit.Base.method(aRecord, 'directLogins'), MochiKit.Base.values, -// function (aValue) { console.log("-> DirectLogin Values", aValue); return aValue; }, -// MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.method(this, 'addField')), + MochiKit.Base.method(aRecord, 'directLogins'), MochiKit.Base.values, +function (aValue) { console.log("-> DirectLogin Values", aValue); return aValue; }, + MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.method(this, 'addDirectLogin')), +//function (aValue) { console.log("-> DirectLogin Values", aValue); return aValue; }, // Clipperz.Async.collectAll, MochiKit.Base.bind(function () { return this; }, this) diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/Cards/Edit.js b/frontend/delta/js/Clipperz/PM/UI/Components/Cards/Edit.js index 9976e34..ee7898e 100644 --- a/frontend/delta/js/Clipperz/PM/UI/Components/Cards/Edit.js +++ b/frontend/delta/js/Clipperz/PM/UI/Components/Cards/Edit.js @@ -33,12 +33,201 @@ Clipperz.PM.UI.Components.Cards.Edit = React.createClass({ // 'loading': React.PropTypes.bool, }, + getInitialState: function() { + return { + 'draggedFieldReference': null, + 'fromFieldPosition': -1, + 'toFieldPosition': -1, + 'dropPosition': -1, + }; + }, + //---------------------------------------------------------------------------- record: function () { return this.props['_record']; }, + fields: function () { + return this.props['fields']; + }, +/* + sortedFields: function () { + var result; + var from = this.state['fromFieldPosition']; + var to = this.state['toFieldPosition']; + +// console.log("FIELDS", this.fields()); + if ((from != -1) && (to != -1) && (from != to)) { + result = MochiKit.Base.clone(this.fields()); + result.splice(to, 0, result.splice(from, 1)[0]); + } else { + result = this.fields(); + } + + return result; + }, +*/ + //============================================================================ + + positionOfField: function (aFieldReference) { + return MochiKit.Base.map(MochiKit.Base.itemgetter('_reference'), this.fields()).indexOf(aFieldReference); + }, + + //============================================================================ + + dragStart: function (anEvent) { + var fieldReference = anEvent.currentTarget.dataset['reference']; + var fieldPosition = this.positionOfField(fieldReference); + + var x = anEvent.clientX - anEvent.currentTarget.getBoundingClientRect().left; + var y = anEvent.clientY - anEvent.currentTarget.getBoundingClientRect().top; + anEvent.dataTransfer.setDragImage(anEvent.currentTarget, x, y); + + MochiKit.Async.callLater(0.1, MochiKit.Base.bind(this.setState, this, { + 'draggedFieldReference': fieldReference, + 'fromFieldPosition': fieldPosition, + 'toFieldPosition': -1, + 'dropPosition': -1 + })); + +// this.setState({ +// 'draggedFieldReference': fieldReference, +// 'fromFieldPosition': fieldPosition +/// 'toFieldPosition': 0 +// }); + +// anEvent.dataTransfer.effectAllowed = 'move'; +// anEvent.dataTransfer.setData('text/html', this.innerHTML); +// anEvent.dropEffect + }, +/* + drag: function (anEvent) { +//console.log("DRAG", anEvent); + }, + drop: function (anEvent) { +console.log("DROP"); //, anEvent); + }, +*/ + dragEnd: function (anEvent) { + if (this.state['toFieldPosition'] != -1) { + var reference = this.props['_reference']; +//console.log("MOVE FIELD POSITION", this.state['toFieldPosition'], this.state['draggedFieldReference']); + Clipperz.Async.callbacks("Clipperz.PM.UI.Components.Cards.Edit.dragEnd-moveFieldToPosition", [ + MochiKit.Base.method(this.record(), 'moveFieldToPosition', this.state['draggedFieldReference'], this.state['toFieldPosition']), + MochiKit.Base.partial(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'refreshCardEditDetail', reference), + ], {trace:false}); + } else { +//console.log("CANCELLED FIELD MOVE"); + } + + this.setState({ + 'draggedFieldReference': null, + 'fromFieldPosition': -1, + 'toFieldPosition': -1, + 'dropPosition': -1 + }) + }, + + //............................................................................ +/* + dragEnter: function (anEvent) { +//console.log("DRAG ENTER", anEvent.currentTarget.dataset['reference'], this.positionOfField(anEvent.currentTarget.dataset['reference'])); +// this.setState({'toFieldPosition': this.positionOfField(anEvent.currentTarget.dataset['reference'])}); + }, +*/ + dragOver: function (anEvent) { + var toFieldPosition; + var dropPosition; + + if (typeof(anEvent.currentTarget.dataset['index']) != 'undefined') { + var y = anEvent.clientY - anEvent.currentTarget.getBoundingClientRect().top; + var h = anEvent.currentTarget.getBoundingClientRect().height; + var hoveringIndex; + var draggingIndex; + var isHoveringTopPart; + + hoveringIndex = +anEvent.currentTarget.dataset['index']; + draggingIndex = +this.state['fromFieldPosition']; + + isHoveringTopPart = (y < h/2); + + if (isHoveringTopPart) { + dropPosition = hoveringIndex; + } else { + dropPosition = hoveringIndex + 1; + } + + if (hoveringIndex > draggingIndex) { + dropPosition = dropPosition - 1; + } + + toFieldPosition = -1; +//console.log(hoveringIndex, draggingIndex, isHoveringTopPart, dropPosition); +//console.log("isHoveringTopPart", isHoveringTopPart); + } else { + dropPosition = anEvent.currentTarget.dataset['dropIndex']; + toFieldPosition = dropPosition; + } + + if ((dropPosition != this.state['dropPosition']) || (toFieldPosition != this.state['toFieldPosition'])) { + this.setState({'dropPosition': dropPosition, 'toFieldPosition':toFieldPosition}); + } + + anEvent.stopPropagation(); + }, +/* + dragLeave: function (anEvent) { +//console.log("DRAG LEAVE", anEvent.currentTarget.dataset['reference'], this.positionOfField(anEvent.currentTarget.dataset['reference'])); +// this.setState({'dropPosition': -1}); + }, +*/ + //============================================================================ +/* + dragStartDropTarget: function (anEvent) { +//console.log("TARGET: DRAG START"); + }, + + dragDropTarget: function (anEvent) { +//console.log("TARGET: DRAG"); + }, + + dropDropTarget: function (anEvent) { +//console.log("TARGET: DROP"); + }, + + dragEndDropTarget: function (anEvent) { +//console.log("TARGET: DRAG END"); + }, + + //............................................................................ + + dragEnterDropTarget: function (anEvent) { +//console.log("TARGET: DRAG ENTER"); + }, +*/ + dragOverDropTarget: function (anEvent) { + var toFieldPosition = anEvent.currentTarget.dataset['dropIndex']; + + if (toFieldPosition != this.state['toFieldPosition']) { +//console.log("TARGET: DRAG OVER - READY TO DROP", anEvent.currentTarget.dataset['dropIndex']); + this.setState({'toFieldPosition':toFieldPosition}); + } + + anEvent.stopPropagation(); + }, + + dragLeaveDropTarget: function (anEvent) { +//console.log("TARGET: DRAG LEAVE"); + if (-1 != this.state['toFieldPosition']) { +//console.log("READY TO DROP", anEvent.currentTarget.dataset['dropIndex']); + MochiKit.Async.callLater(0.5, MochiKit.Base.bind(function () { +//console.log("TARGET: DRAG LEAVE #####"); + this.setState({'toFieldPosition':-1}); + }, this)) + } + }, + //============================================================================ handleChange: function (anObject , aMethodName) { @@ -104,12 +293,27 @@ Clipperz.PM.UI.Components.Cards.Edit = React.createClass({ cardFieldClasses['cardField'] = true; cardFieldClasses[aField['actionType']] = true; cardFieldClasses['hidden'] = aField['isHidden']; - + if (this.state['draggedFieldReference'] == aField['_reference']) { + cardFieldClasses['dragged'] = true; + } + cardFieldValueClasses['fieldValue'] = true; cardFieldValueClasses[aField['actionType']] = true; cardFieldValueClasses['hidden'] = aField['isHidden']; - - return React.DOM.div({'className':React.addons.classSet(cardFieldClasses), 'key':ref}, [ + + return React.DOM.div({'className':React.addons.classSet(cardFieldClasses), 'draggable':true, 'key':ref, + 'data-reference':ref, + 'data-document-id':ref, + 'data-index':this.positionOfField(ref), + + 'onDragStart':this.dragStart, +// 'onDrag':this.drag, +// 'onDragEnter':this.dragEnter, + 'onDragOver':this.dragOver, +// 'onDragLeave':this.dragLeave, +// 'onDrop':this.drop, + 'onDragEnd':this.dragEnd + }, [ React.DOM.div({'className':'fieldValues'}, [ React.DOM.span({'className':'removeField', 'onClick':this.removeField(field)}, "delete"), React.DOM.input({'className':'fieldLabel', 'onChange':this.handleChange(field, 'setLabel'), 'defaultValue':aField['label']}), @@ -119,8 +323,40 @@ Clipperz.PM.UI.Components.Cards.Edit = React.createClass({ ]); }, + updateRenderedFieldsWithDropArea: function (someRenderedFields) { + var dragFrom = this.state['fromFieldPosition'] + var dropTo = this.state['dropPosition']; + + var dropAreaPositionIndex = dropTo != -1 ? dropTo : dragFrom; + var dropArea = React.DOM.div({'className':'dropArea', 'key':'fieldDropArea', + 'data-drop-index':dropAreaPositionIndex, + +// 'onDragStart':this.dragStartDropTarget, +// 'onDrag':this.dragDropTarget, +// 'onDragEnter':this.dragEnterDropTarget, + 'onDragOver': this.dragOverDropTarget, + 'onDragLeave': this.dragLeaveDropTarget, +// 'onDrop': this.dropDropTarget, +// 'onDragEnd':this.dragEndDropTarget + + }); + + var dropAreaNodeIndex = (dropAreaPositionIndex < dragFrom) ? dropAreaPositionIndex : dropAreaPositionIndex + 1; +//console.log("DROP", dropTo, dropAreaPositionIndex); + someRenderedFields.splice(dropAreaNodeIndex, 0, dropArea); + + return someRenderedFields; + }, + renderFields: function (someFields) { - return React.DOM.div({'className':'cardFields'}, MochiKit.Base.map(this.renderField, someFields)); + var renderedFields; + + renderedFields = MochiKit.Base.map(this.renderField, someFields); + + if (this.state['draggedFieldReference'] != null) { + renderedFields = this.updateRenderedFieldsWithDropArea(renderedFields); + } + return React.DOM.div({'className':'cardFields' /*, 'dropzone':'move'*/}, renderedFields); }, renderAddNewField: function () { @@ -156,7 +392,7 @@ Clipperz.PM.UI.Components.Cards.Edit = React.createClass({ this.renderLabel(this.props['label']), this.renderTags(this.props['tags']), this.renderNotes(this.props['notes']), - this.renderFields(this.props['fields']), + this.renderFields(this.fields()), this.renderAddNewField(), this.renderDirectLogins(this.props['directLogins']) ]) diff --git a/frontend/delta/scss/clipperz.scss b/frontend/delta/scss/clipperz.scss index 40f8631..0dad85a 100644 --- a/frontend/delta/scss/clipperz.scss +++ b/frontend/delta/scss/clipperz.scss @@ -34,3 +34,5 @@ body:after { //@import "sizes/wide"; //@import "sizes/extra-wide"; //@import "sizes/extra-short"; + + diff --git a/frontend/delta/scss/core/layout.scss b/frontend/delta/scss/core/layout.scss index 5f91d50..fad71ad 100644 --- a/frontend/delta/scss/core/layout.scss +++ b/frontend/delta/scss/core/layout.scss @@ -432,6 +432,9 @@ div.dialogBox { width: 100%; height: 100%; + .mask { + z-index: 12; + } div.dialog { @include flex(none); z-index: 99999; diff --git a/frontend/delta/scss/style/card.scss b/frontend/delta/scss/style/card.scss index 60c38c9..f034c54 100644 --- a/frontend/delta/scss/style/card.scss +++ b/frontend/delta/scss/style/card.scss @@ -249,10 +249,22 @@ $cardViewBasePadding: 10px; padding: $cardViewBasePadding; } + .dropArea { + border: 3px dashed red; + width: 100%; + height: 40px; + } + .cardField { @include flexbox; @include flex-direction(row); + + background-color: lightgreen; + &.dragged { +// border: 4px dotted red; + display: none; + } .fieldValues { @include flex(1); padding: $cardViewBasePadding; diff --git a/frontend/delta/tests/tests/Clipperz/PM/DataModel/Record.test.js b/frontend/delta/tests/tests/Clipperz/PM/DataModel/Record.test.js index f9f8fde..7007288 100644 --- a/frontend/delta/tests/tests/Clipperz/PM/DataModel/Record.test.js +++ b/frontend/delta/tests/tests/Clipperz/PM/DataModel/Record.test.js @@ -1632,7 +1632,7 @@ deferredResult.addCallback(function (aValue) { console.log("FIELDS", aValue); re return deferredResult; }, - //------------------------------------------------------------------------- + //------------------------------------------------------------------------- 'editTags_remove': function (someTestArgs) { var deferredResult; @@ -1674,9 +1674,283 @@ deferredResult.addCallback(function (aValue) { console.log("FIELDS", aValue); re return deferredResult; }, - //------------------------------------------------------------------------- + //------------------------------------------------------------------------- - 'syntaxFix': MochiKit.Base.noop + 'changeFieldOrder': function (someTestArgs) { + var deferredResult; + var proxy; + var user; + var user2; + var recordID = 'd620764a656bfd4e1d3758500d5db72e460a0cf729d56ed1a7755b5725c50045' +// var directLoginID = 'dba0db679802f0e6aa6d0b7a6aaf42350aabc5f057409edd99a268a92ebb6496'; + var originalFieldReference = '31d750b1944b65454a47ab10ad8b04ce99464c68b5f85bb015817ae7433b3940'; + + proxy = new Clipperz.PM.Proxy.Test({shouldPayTolls:false, isDefault:true, readOnly:false}); + user = new Clipperz.PM.DataModel.User({username:'joe', getPassphraseFunction:function () { return 'clipperz';}}); + user2 = new Clipperz.PM.DataModel.User({username:'joe', getPassphraseFunction:function () { return 'clipperz';}}); + + deferredResult = new Clipperz.Async.Deferred("Record.test.changeFieldOrder", someTestArgs); + deferredResult.addMethod(proxy.dataStore(), 'setupWithEncryptedData', testData['joe_clipperz_offline_copy_data']); + deferredResult.addMethod(user, 'login'); + deferredResult.addMethod(user, 'getRecords'); + deferredResult.addMethod(user, 'getRecord', recordID); + deferredResult.addMethodcaller('getFieldsValues'); + deferredResult.addCallback(MochiKit.Base.values); + deferredResult.addCallback(MochiKit.Base.map, MochiKit.Base.itemgetter('label')); + deferredResult.addTest([ + "AAdvantage N.", + "Password", + "Web site", // * + "Call center", + "Expire date" + ], "initial order of keys", true); + + deferredResult.addMethod(user, 'getRecord', recordID); + deferredResult.addMethodcaller('fieldWithLabel', "Web site"); + deferredResult.addMethodcaller('reference'); + deferredResult.addTest(originalFieldReference, "Expected field reference"); + + deferredResult.addMethod(user, 'getRecord', recordID); + deferredResult.addMethodcaller('moveFieldToPosition', originalFieldReference, 0); + + deferredResult.addMethod(user, 'hasPendingChanges'); + deferredResult.addTest(true, "after changing the order of fields in a record, the user should report pending changes") + + deferredResult.addMethod(user, 'getRecord', recordID); + deferredResult.addMethodcaller('hasPendingChanges'); + deferredResult.addTest(true, "after changing the order of fields, also the record should report pending changes") + + deferredResult.addMethod(user, 'getRecord', recordID); + deferredResult.addMethodcaller('getFieldsValues'); + deferredResult.addCallback(MochiKit.Base.values); + deferredResult.addCallback(MochiKit.Base.map, MochiKit.Base.itemgetter('label')); + deferredResult.addCallback(function (aValue) { console.log("[1] -> 0", aValue); return aValue; }); + deferredResult.addTest([ + "Web site", // * + "AAdvantage N.", + "Password", + "Call center", + "Expire date" + ], "final order of keys [1]", true); + + deferredResult.addMethod(user, 'saveChanges'); + + deferredResult.addMethod(user2, 'login'); + deferredResult.addMethod(user, 'getRecord', recordID); + deferredResult.addMethodcaller('getFieldsValues'); + deferredResult.addCallback(MochiKit.Base.values); + deferredResult.addCallback(MochiKit.Base.map, MochiKit.Base.itemgetter('label')); + deferredResult.addCallback(function (aValue) { console.log("[2] -> 0", aValue); return aValue; }); + deferredResult.addTest([ + "Web site", // * + "AAdvantage N.", + "Password", + "Call center", + "Expire date" + ], "final order of keys [2]", true); + + deferredResult.callback(); + + return deferredResult; + }, + + //------------------------------------------------------------------------- + + 'changeFieldOrderToLast': function (someTestArgs) { + var deferredResult; + var proxy; + var user; + var user2; + var recordID = 'd620764a656bfd4e1d3758500d5db72e460a0cf729d56ed1a7755b5725c50045' +// var directLoginID = 'dba0db679802f0e6aa6d0b7a6aaf42350aabc5f057409edd99a268a92ebb6496'; + var originalFieldReference = '31d750b1944b65454a47ab10ad8b04ce99464c68b5f85bb015817ae7433b3940'; + + proxy = new Clipperz.PM.Proxy.Test({shouldPayTolls:false, isDefault:true, readOnly:false}); + user = new Clipperz.PM.DataModel.User({username:'joe', getPassphraseFunction:function () { return 'clipperz';}}); + user2 = new Clipperz.PM.DataModel.User({username:'joe', getPassphraseFunction:function () { return 'clipperz';}}); + + deferredResult = new Clipperz.Async.Deferred("Record.test.changeFieldOrderToLast", someTestArgs); + deferredResult.addMethod(proxy.dataStore(), 'setupWithEncryptedData', testData['joe_clipperz_offline_copy_data']); + deferredResult.addMethod(user, 'login'); + deferredResult.addMethod(user, 'getRecord', recordID); + deferredResult.addMethodcaller('getFieldsValues'); + deferredResult.addCallback(MochiKit.Base.values); + deferredResult.addCallback(MochiKit.Base.map, MochiKit.Base.itemgetter('label')); + deferredResult.addTest([ + "AAdvantage N.", + "Password", + "Web site", // * + "Call center", + "Expire date", + ], "initial order of keys", true); + + deferredResult.addMethod(user, 'getRecord', recordID); + deferredResult.addMethodcaller('moveFieldToPosition', originalFieldReference, 0); + deferredResult.addMethod(user, 'getRecord', recordID); + deferredResult.addMethodcaller('getFieldsValues'); + deferredResult.addCallback(MochiKit.Base.values); + deferredResult.addCallback(MochiKit.Base.map, MochiKit.Base.itemgetter('label')); + deferredResult.addCallback(function (aValue) { console.log("[3] -> 0", aValue); return aValue; }); + deferredResult.addTest([ + "Web site", // * + "AAdvantage N.", + "Password", + "Call center", + "Expire date", + ], "final order of keys [3]", true); + deferredResult.addMethod(user, 'revertChanges'); + + deferredResult.addMethod(user, 'getRecord', recordID); + deferredResult.addMethodcaller('moveFieldToPosition', originalFieldReference, 1); + deferredResult.addMethod(user, 'getRecord', recordID); + deferredResult.addMethodcaller('getFieldsValues'); + deferredResult.addCallback(MochiKit.Base.values); + deferredResult.addCallback(MochiKit.Base.map, MochiKit.Base.itemgetter('label')); + deferredResult.addCallback(function (aValue) { console.log("[4] -> 1", aValue); return aValue; }); + deferredResult.addTest([ + "AAdvantage N.", + "Web site", // * + "Password", + "Call center", + "Expire date", + ], "final order of keys [4]", true); + deferredResult.addMethod(user, 'revertChanges'); + + deferredResult.addMethod(user, 'getRecord', recordID); + deferredResult.addMethodcaller('moveFieldToPosition', originalFieldReference, 2); + deferredResult.addMethod(user, 'getRecord', recordID); + deferredResult.addMethodcaller('getFieldsValues'); + deferredResult.addCallback(MochiKit.Base.values); + deferredResult.addCallback(MochiKit.Base.map, MochiKit.Base.itemgetter('label')); + deferredResult.addCallback(function (aValue) { console.log("[5] -> 2", aValue); return aValue; }); + deferredResult.addTest([ + "AAdvantage N.", + "Password", + "Web site", // * + "Call center", + "Expire date", + ], "final order of keys [5]", true); + deferredResult.addMethod(user, 'revertChanges'); + + deferredResult.addMethod(user, 'getRecord', recordID); + deferredResult.addMethodcaller('moveFieldToPosition', originalFieldReference, 3); + deferredResult.addMethod(user, 'getRecord', recordID); + deferredResult.addMethodcaller('getFieldsValues'); + deferredResult.addCallback(MochiKit.Base.values); + deferredResult.addCallback(MochiKit.Base.map, MochiKit.Base.itemgetter('label')); + deferredResult.addCallback(function (aValue) { console.log("[6] -> 3", aValue); return aValue; }); + deferredResult.addTest([ + "AAdvantage N.", + "Password", + "Call center", + "Web site", // * + "Expire date", + ], "final order of keys [6]", true); + deferredResult.addMethod(user, 'revertChanges'); + + deferredResult.addMethod(user, 'getRecord', recordID); + deferredResult.addMethodcaller('moveFieldToPosition', originalFieldReference, 4); + deferredResult.addMethod(user, 'getRecord', recordID); + deferredResult.addMethodcaller('getFieldsValues'); + deferredResult.addCallback(MochiKit.Base.values); + deferredResult.addCallback(MochiKit.Base.map, MochiKit.Base.itemgetter('label')); + deferredResult.addCallback(function (aValue) { console.log("[7] -> 4", aValue); return aValue; }); + deferredResult.addTest([ + "AAdvantage N.", + "Password", + "Call center", + "Expire date", + "Web site", // * + ], "final order of keys [7]", true); + deferredResult.addMethod(user, 'revertChanges'); + + deferredResult.addMethod(user, 'getRecord', recordID); + deferredResult.addMethodcaller('moveFieldToPosition', originalFieldReference, 10); + deferredResult.addMethod(user, 'getRecord', recordID); + deferredResult.addMethodcaller('getFieldsValues'); + deferredResult.addCallback(MochiKit.Base.values); + deferredResult.addCallback(MochiKit.Base.map, MochiKit.Base.itemgetter('label')); + deferredResult.addCallback(function (aValue) { console.log("[8] -> 10 -> 4", aValue); return aValue; }); + deferredResult.addTest([ + "AAdvantage N.", + "Password", + "Call center", + "Expire date", + "Web site", // * + ], "final order of keys [8]", true); + deferredResult.addMethod(user, 'revertChanges'); + + deferredResult.callback(); + + return deferredResult; + }, + + //------------------------------------------------------------------------- + + 'changeFieldOrderSettingSamePosition': function (someTestArgs) { + var deferredResult; + var proxy; + var user; + var user2; + var recordID = 'd620764a656bfd4e1d3758500d5db72e460a0cf729d56ed1a7755b5725c50045' +// var directLoginID = 'dba0db679802f0e6aa6d0b7a6aaf42350aabc5f057409edd99a268a92ebb6496'; + var originalFieldReference = '31d750b1944b65454a47ab10ad8b04ce99464c68b5f85bb015817ae7433b3940'; + + proxy = new Clipperz.PM.Proxy.Test({shouldPayTolls:false, isDefault:true, readOnly:false}); + user = new Clipperz.PM.DataModel.User({username:'joe', getPassphraseFunction:function () { return 'clipperz';}}); + user2 = new Clipperz.PM.DataModel.User({username:'joe', getPassphraseFunction:function () { return 'clipperz';}}); + + deferredResult = new Clipperz.Async.Deferred("Record.test.changeFieldOrderSettingSamePosition", someTestArgs); + deferredResult.addMethod(proxy.dataStore(), 'setupWithEncryptedData', testData['joe_clipperz_offline_copy_data']); + deferredResult.addMethod(user, 'login'); + deferredResult.addMethod(user, 'getRecords'); + deferredResult.addMethod(user, 'getRecord', recordID); + deferredResult.addMethodcaller('getFieldsValues'); + deferredResult.addCallback(MochiKit.Base.values); + deferredResult.addCallback(MochiKit.Base.map, MochiKit.Base.itemgetter('label')); + deferredResult.addTest([ + "AAdvantage N.", + "Password", + "Web site", // * + "Call center", + "Expire date" + ], "initial order of keys", true); + + deferredResult.addMethod(user, 'getRecord', recordID); + deferredResult.addMethodcaller('fieldWithLabel', "Web site"); + deferredResult.addMethodcaller('reference'); + deferredResult.addTest(originalFieldReference, "Expected field reference"); + + deferredResult.addMethod(user, 'getRecord', recordID); + deferredResult.addMethodcaller('moveFieldToPosition', originalFieldReference, 2); + deferredResult.addMethod(user, 'hasPendingChanges'); + deferredResult.addTest(false, "changing the position of a field to its previous place should not trigger any changes"); + + deferredResult.addMethod(user, 'getRecord', recordID); + deferredResult.addMethodcaller('hasPendingChanges'); + deferredResult.addTest(false, "also the record should not have any pending changes"); + + deferredResult.addMethod(user, 'getRecord', recordID); + deferredResult.addMethodcaller('getFieldsValues'); + deferredResult.addCallback(MochiKit.Base.values); + deferredResult.addCallback(MochiKit.Base.map, MochiKit.Base.itemgetter('label')); + deferredResult.addCallback(function (aValue) { console.log("[9]", aValue); return aValue; }); + deferredResult.addTest([ + "AAdvantage N.", + "Password", + "Web site", // * + "Call center", + "Expire date" + ], "final order of keys [9]", true); + + deferredResult.callback(); + + return deferredResult; + }, + + //------------------------------------------------------------------------- + + 'syntaxFix': MochiKit.Base.noop }; diff --git a/frontend/delta/tests/tests/Clipperz/PM/DataModel/User.test.js b/frontend/delta/tests/tests/Clipperz/PM/DataModel/User.test.js index 8303c43..3af3358 100644 --- a/frontend/delta/tests/tests/Clipperz/PM/DataModel/User.test.js +++ b/frontend/delta/tests/tests/Clipperz/PM/DataModel/User.test.js @@ -1163,13 +1163,46 @@ var tests = { deferredResult.addMethodcaller('hasLoadedRemoteData'); deferredResult.addTest(false, "After saving, record_2 should still be NOT loaded"); - deferredResult.callback(); return deferredResult; }, - //------------------------------------------------------------------------- + //------------------------------------------------------------------------- + + 'doNotLoadDataJustToAnswerHasPendingChanges_test': function (someTestArgs) { + var deferredResult; + var proxy; + var user; + var record_1; + + record_1 = '062af892bcfba49ffcff05c56d99b7af2d508358e39c058c2e1fc83531436f80'; + + proxy = new Clipperz.PM.Proxy.Test({shouldPayTolls:true, isDefault:true, readOnly:false}); + user = new Clipperz.PM.DataModel.User({username:'joe', getPassphraseFunction:function () { return 'clipperz';}}); + + deferredResult = new Clipperz.Async.Deferred("doNotLoadDataJustToAnswerHasPendingChanges_test", someTestArgs); + deferredResult.addMethod(proxy.dataStore(), 'setupWithEncryptedData', testData['joe_clipperz_offline_copy_data']); + deferredResult.addMethod(user, 'login'); + + deferredResult.addMethod(user, 'getRecord', record_1); + deferredResult.addMethodcaller('hasLoadedRemoteData'); + deferredResult.addTest(false, "The card data should have not been loaded yet"); + + deferredResult.addMethod(user, 'getRecord', record_1); + deferredResult.addMethodcaller('hasPendingChanges'); + deferredResult.addTest(false, "record_1 shoud not have any changes"); + + deferredResult.addMethod(user, 'getRecord', record_1); + deferredResult.addMethodcaller('hasLoadedRemoteData'); + deferredResult.addTest(false, "record_1 should not have loaded data just to answer the 'hasPendingChanges' method invocation"); + + deferredResult.callback(); + + return deferredResult; + }, + + //------------------------------------------------------------------------- 'addNewRecordFieldAndSave_test': function (someTestArgs) { var deferredResult; @@ -2080,6 +2113,14 @@ console.log("PROXY", proxy); deferredResult.addCallback(MochiKit.Base.itemgetter('length')); deferredResult.addTest(1, "The cloned record has 1 direct logins"); + deferredResult.addCallback(function () { return clonedRecordID; }) + deferredResult.addMethod(user, 'getRecord'); + deferredResult.addMethodcaller('directLogins'); + deferredResult.addCallback(MochiKit.Base.values); + deferredResult.addCallback(MochiKit.Base.itemgetter('0')); + deferredResult.addMethodcaller('label'); + deferredResult.addTest("Amazon.com", "Label of the newly clone Direct Login"); + deferredResult.callback(); return deferredResult; @@ -2108,7 +2149,7 @@ console.log("PROXY", proxy); deferredResult.addTest(20, "This account has 20 cards"); deferredResult.addMethod(user, 'getRecord', recordID); - deferredResult.addMethodcaller('setLabel', "new value"); + deferredResult.addMethodcaller('addField', { 'hidden': false, 'label': "New field", 'type': "URL", 'value': "http://www.example.com" }); deferredResult.addMethod(user, 'getRecord', recordID); deferredResult.addMethodcaller('hasPendingChanges'); deferredResult.addTest(true, "The record has pending changes.");