From d514de8871b8323961be49ca15067ee6ab344713 Mon Sep 17 00:00:00 2001 From: Damien Pobel Date: Thu, 11 Dec 2014 14:42:50 +0100 Subject: [PATCH 01/13] EZP-23748: change the field edit/content edit form view so that undefined fields are ignored --- .../public/js/views/ez-contenteditformview.js | 12 +++++-- Resources/public/js/views/ez-fieldeditview.js | 18 +++++++--- .../assets/ez-contenteditformview-tests.js | 11 +++++++ .../js/views/assets/ez-fieldeditview-tests.js | 33 +++++++++++++++---- 4 files changed, 59 insertions(+), 15 deletions(-) diff --git a/Resources/public/js/views/ez-contenteditformview.js b/Resources/public/js/views/ez-contenteditformview.js index 3a4d6b8ae..d46d64447 100644 --- a/Resources/public/js/views/ez-contenteditformview.js +++ b/Resources/public/js/views/ez-contenteditformview.js @@ -15,7 +15,8 @@ YUI.add('ez-contenteditformview', function (Y) { var COLLAPSED_CLASS = 'is-collapsed', FIELDSET_FIELDS_CLASS = '.fieldgroup-fields', TRANSITION_DURATION = 0.4, - TRANSITION_EASING = 'ease-in-out'; + TRANSITION_EASING = 'ease-in-out', + L = Y.Lang; /** * The form view @@ -114,7 +115,8 @@ YUI.add('ez-contenteditformview', function (Y) { }, /** - * Returns an array containing the field updated with the user input + * Returns an array containing the field updated with the user input. + * Any undefined field is ignored. * * @method getFields * @return Array @@ -123,7 +125,11 @@ YUI.add('ez-contenteditformview', function (Y) { var res = []; Y.Array.each(this._fieldEditViews, function (val) { - res.push(val.getField()); + var field = val.getField(); + + if ( !L.isUndefined(field) ) { + res.push(field); + } }); return res; }, diff --git a/Resources/public/js/views/ez-fieldeditview.js b/Resources/public/js/views/ez-fieldeditview.js index 2bff78ac0..ca7386738 100644 --- a/Resources/public/js/views/ez-fieldeditview.js +++ b/Resources/public/js/views/ez-fieldeditview.js @@ -262,7 +262,8 @@ YUI.add('ez-fieldeditview', function (Y) { * Returns the value of the field from the current user input. This * method should be implemented in each field edit view. * - * The default implementation returns undefined. + * The default implementation returns undefined. Returning undefined + * means that the field should be ignored when saving the content. * * @method _getFieldValue * @protected @@ -277,15 +278,22 @@ YUI.add('ez-fieldeditview', function (Y) { /** * Returns an updated version of the field containing a field value - * reflecting the current user input + * reflecting the current user input. Returns undefined when the field + * value should not be taken into account. * * @method getField - * @return Object + * @return Object or undefined */ getField: function () { - var field = Y.clone(this.get('field')); + var value = this._getFieldValue(), + field; + + if ( L.isUndefined(value) ) { + return undefined; + } + field = Y.clone(this.get('field')); + field.fieldValue = value; - field.fieldValue = this._getFieldValue(); return field; }, }, { diff --git a/Tests/js/views/assets/ez-contenteditformview-tests.js b/Tests/js/views/assets/ez-contenteditformview-tests.js index c4647d043..f5b83efce 100644 --- a/Tests/js/views/assets/ez-contenteditformview-tests.js +++ b/Tests/js/views/assets/ez-contenteditformview-tests.js @@ -270,6 +270,11 @@ YUI.add('ez-contenteditformview-tests', function (Y) { 'identifier': 'id2', 'fieldType': 'test2', 'fieldGroup': 'testfieldgroup', + }, + 'id3': { + 'identifier': 'id3', + 'fieldType': 'test3', + 'fieldGroup': 'testfieldgroup', } } }); @@ -283,6 +288,11 @@ YUI.add('ez-contenteditformview-tests', function (Y) { return that.field2; } })); + Y.eZ.FieldEditView.registerFieldEditView('test3', Y.Base.create('fieldEdit3', Y.eZ.FieldEditView, [], { + getField: function () { + return undefined; + } + })); Y.Mock.expect(this.version, { method: 'getField', @@ -305,6 +315,7 @@ YUI.add('ez-contenteditformview-tests', function (Y) { delete this.view; Y.eZ.FieldEditView.registerFieldEditView('test1', undefined); Y.eZ.FieldEditView.registerFieldEditView('test2', undefined); + Y.eZ.FieldEditView.registerFieldEditView('test3', undefined); }, "Should return the array of field handled by the form": function () { diff --git a/Tests/js/views/assets/ez-fieldeditview-tests.js b/Tests/js/views/assets/ez-fieldeditview-tests.js index 316afa5d5..55a6fdbc0 100644 --- a/Tests/js/views/assets/ez-fieldeditview-tests.js +++ b/Tests/js/views/assets/ez-fieldeditview-tests.js @@ -380,13 +380,20 @@ YUI.add('ez-fieldeditview-tests', function (Y) { destroy: destroyTooltipPartial, setUp: function () { - var CustomView = Y.Base.create('customView', Y.eZ.FieldEditView, [], { + var CustomView, that = this; + + this.fieldValue = "field value"; + CustomView = Y.Base.create('customView', Y.eZ.FieldEditView, [], { _variables: function () { return { 'foo': 'bar', 'ez': 'publish' }; - } + }, + + _getFieldValue: function () { + return that.fieldValue; + }, }); this.jsonContent = {}; @@ -409,7 +416,10 @@ YUI.add('ez-fieldeditview-tests', function (Y) { }); this.fieldDefinition = {descriptions: {"eng-GB": "Test description"}}; - this.field = {descriptions: {}}; + this.field = { + fieldValue: this.fieldValue, + descriptions: {}, + }; this.view = new CustomView({ container: '.container', @@ -466,18 +476,27 @@ YUI.add('ez-fieldeditview-tests', function (Y) { this.view.render(); }, - "getField should return a clone value with 'undefined' as a value": function () { + "getField should return a clone of the field": function () { var updatedField = this.view.getField(); Y.Assert.areNotSame( this.field, updatedField, "getField should 'clone' the field" ); - Y.Assert.isUndefined( + Y.Assert.areEqual( + this.fieldValue, updatedField.fieldValue, - "The field value should be undefined" + "The field value should be kept" ); - } + }, + + "getField should return undefined if _getFieldValue returns undefined": function () { + var updatedField; + + this.fieldValue = undefined; + updatedField = this.view.getField(); + Y.Assert.isUndefined(updatedField, "The field should be undefined"); + }, }); registryTest = new Y.Test.Case({ From 37e9f104684e6e236cf510c2dea4eb44f3dde4f8 Mon Sep 17 00:00:00 2001 From: Damien Pobel Date: Thu, 11 Dec 2014 14:43:42 +0100 Subject: [PATCH 02/13] EZP-23748: Changed the content edit form view to be bubble target of the field edit views --- .../public/js/views/ez-contenteditformview.js | 23 ++++++++++--------- .../assets/ez-contenteditformview-tests.js | 12 ++++++++++ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/Resources/public/js/views/ez-contenteditformview.js b/Resources/public/js/views/ez-contenteditformview.js index d46d64447..741e4e294 100644 --- a/Resources/public/js/views/ez-contenteditformview.js +++ b/Resources/public/js/views/ez-contenteditformview.js @@ -55,23 +55,24 @@ YUI.add('ez-contenteditformview', function (Y) { version = this.get('version'), contentType = this.get('contentType'), fieldDefinitions = contentType.get('fieldDefinitions'), - views = []; + views = [], + that = this; Y.Object.each(fieldDefinitions, function (def) { - var EditView; + var EditView, view; try { EditView = Y.eZ.FieldEditView.getFieldEditView(def.fieldType); - views.push( - new EditView({ - content: content, - version: version, - contentType: contentType, - fieldDefinition: def, - field: version.getField(def.identifier) - }) - ); + view = new EditView({ + content: content, + version: version, + contentType: contentType, + fieldDefinition: def, + field: version.getField(def.identifier) + }); + views.push(view); + view.addTarget(that); } catch (e) { console.error(e.message); } diff --git a/Tests/js/views/assets/ez-contenteditformview-tests.js b/Tests/js/views/assets/ez-contenteditformview-tests.js index f5b83efce..da9037c8b 100644 --- a/Tests/js/views/assets/ez-contenteditformview-tests.js +++ b/Tests/js/views/assets/ez-contenteditformview-tests.js @@ -13,6 +13,7 @@ YUI.add('ez-contenteditformview-tests', function (Y) { Y.eZ.FieldEditView.registerFieldEditView('test1', Y.Base.create('test1FieldEditView', Y.View, [], { render: function () { this.get('container').setContent('test1 rendered'); + this.fire('test1Event'); return this; } })); @@ -115,6 +116,17 @@ YUI.add('ez-contenteditformview-tests', function (Y) { this.view.render(); }, + "Should add the form view as a bubble target of the field edit view": function () { + var bubble = false; + + this.view.on('*:test1Event', function () { + bubble = true; + }); + this.view.render(); + + Y.Assert.isTrue(bubble, "The field edit view event should bubble to the form view"); + }, + "Should collapse and remove collapsing of a fieldset once repeatedly tapped": function () { var fieldGroupName, fieldGroupFields, that = this; From e6b315c9cfde4a62ed18c7067ccf1a56c2e60ff5 Mon Sep 17 00:00:00 2001 From: Damien Pobel Date: Fri, 12 Dec 2014 12:14:20 +0100 Subject: [PATCH 03/13] EZP-23748: Changed the GetFieldTests class to be more flexible --- .../js/views/fields/assets/getfield-tests.js | 59 ++++++++++++------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/Tests/js/views/fields/assets/getfield-tests.js b/Tests/js/views/fields/assets/getfield-tests.js index b68e502a6..28aec2d02 100644 --- a/Tests/js/views/fields/assets/getfield-tests.js +++ b/Tests/js/views/fields/assets/getfield-tests.js @@ -5,46 +5,63 @@ YUI.add('getfield-tests', function (Y) { Y.namespace('eZ.Test'); + var L = Y.Lang; + Y.eZ.Test.GetFieldTests = { name: "getField Tests", setUp: function () { - var content, contentType, version; - - content = new Y.Mock(); - contentType = new Y.Mock(); - version = new Y.Mock(); - Y.Mock.expect(content, { - method: 'toJSON', - returns: {} - }); - Y.Mock.expect(contentType, { - method: 'toJSON', - returns: {} - }); - Y.Mock.expect(version, { - method: 'toJSON', - returns: {} - }); + if ( !this.content ) { + this.content = new Y.Mock(); + Y.Mock.expect(this.content, { + method: 'toJSON', + returns: {} + }); + } + if ( !this.contentType ) { + this.contentType = new Y.Mock(); + Y.Mock.expect(this.contentType, { + method: 'toJSON', + returns: {} + }); + } + if ( !this.version ) { + this.version = new Y.Mock(); + Y.Mock.expect(this.version, { + method: 'toJSON', + returns: {} + }); + } + if ( L.isUndefined(this.fieldValue) ) { + this.fieldValue = ""; + } this.view = new this.ViewConstructor({ container: '.container', field: { fieldDefinitionIdentifier: "name", id: 186, - fieldValue: "", + fieldValue: this.fieldValue, languageCode: "eng-GB" }, fieldDefinition: this.fieldDefinition, - content: content, - version: version, - contentType: contentType + content: this.content, + version: this.version, + contentType: this.contentType }); + this._afterSetup(); + }, + + _afterSetup: function () { }, tearDown: function () { this.view.destroy(); delete this.view; + this._afterTearnDown(); + }, + + _afterTearnDown: function () { }, _assertCorrectFieldValue: function (fieldValue, msg) { From 8095d7e4e54cbb807a92768303a1c10e8e35649a Mon Sep 17 00:00:00 2001 From: Damien Pobel Date: Fri, 12 Dec 2014 12:15:07 +0100 Subject: [PATCH 04/13] EZP-23748: Made AsynchronousView _watchAttribute optional --- .../public/js/extensions/ez-asynchronousview.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Resources/public/js/extensions/ez-asynchronousview.js b/Resources/public/js/extensions/ez-asynchronousview.js index 1abcce0a0..d8464dfcb 100644 --- a/Resources/public/js/extensions/ez-asynchronousview.js +++ b/Resources/public/js/extensions/ez-asynchronousview.js @@ -56,9 +56,11 @@ YUI.add('ez-asynchronousview', function (Y) { this.render(); }); - this.after(this._watchAttribute + 'Change', function (e) { - this.render(); - }); + if ( this._watchAttribute ) { + this.after(this._watchAttribute + 'Change', function (e) { + this.render(); + }); + } }, /** @@ -76,7 +78,9 @@ YUI.add('ez-asynchronousview', function (Y) { loadingError: false }; - attrs[this._watchAttribute] = null; + if ( this._watchAttribute ) { + attrs[this._watchAttribute] = null; + } this.setAttrs(attrs); this._fireMethod.call(this); }, From 61998bb75b7cca049d2cdd6a3d2de06208ad7d3a Mon Sep 17 00:00:00 2001 From: Damien Pobel Date: Fri, 12 Dec 2014 12:17:03 +0100 Subject: [PATCH 05/13] EZP-23748: Added the ImageVariationLoad plugin to the content edit view service --- .../js/views/services/plugins/ez-imagevariationloadplugin.js | 2 +- .../plugins/assets/ez-imagevariationloadplugin-tests.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/public/js/views/services/plugins/ez-imagevariationloadplugin.js b/Resources/public/js/views/services/plugins/ez-imagevariationloadplugin.js index 52d839fe6..403ece4c0 100644 --- a/Resources/public/js/views/services/plugins/ez-imagevariationloadplugin.js +++ b/Resources/public/js/views/services/plugins/ez-imagevariationloadplugin.js @@ -55,6 +55,6 @@ YUI.add('ez-imagevariationloadplugin', function (Y) { }); Y.eZ.PluginRegistry.registerPlugin( - Y.eZ.Plugin.ImageVariationLoad, ['locationViewViewService'] + Y.eZ.Plugin.ImageVariationLoad, ['locationViewViewService', 'contentEditViewService'] ); }); diff --git a/Tests/js/views/services/plugins/assets/ez-imagevariationloadplugin-tests.js b/Tests/js/views/services/plugins/assets/ez-imagevariationloadplugin-tests.js index d1c3f9c85..f9165bd1d 100644 --- a/Tests/js/views/services/plugins/assets/ez-imagevariationloadplugin-tests.js +++ b/Tests/js/views/services/plugins/assets/ez-imagevariationloadplugin-tests.js @@ -106,7 +106,7 @@ YUI.add('ez-imagevariationloadplugin-tests', function (Y) { registerTest = new Y.Test.Case(Y.eZ.Test.PluginRegisterTest); registerTest.Plugin = Y.eZ.Plugin.ImageVariationLoad; - registerTest.components = ['locationViewViewService']; + registerTest.components = ['locationViewViewService', 'contentEditViewService']; Y.Test.Runner.setName("eZ Image Variation Load Plugin tests"); Y.Test.Runner.add(tests); From 29d994d08ab172eb8efc69960cd1b43ebbcb4e26 Mon Sep 17 00:00:00 2001 From: Damien Pobel Date: Fri, 12 Dec 2014 12:18:07 +0100 Subject: [PATCH 06/13] EZP-23748: Implemented the Image field edit view --- Resources/config/css.yml | 2 + Resources/config/image_variations.yml | 4 + Resources/config/yui.yml | 7 + Resources/public/css/modules/button.css | 13 + Resources/public/css/theme/modules/button.css | 5 + .../css/theme/views/fields/edit/image.css | 82 ++ .../public/css/views/fields/edit/image.css | 98 ++ Resources/public/fonts/icomoon.eot | Bin 7928 -> 8020 bytes Resources/public/fonts/icomoon.svg | 1 + Resources/public/fonts/icomoon.ttf | Bin 7764 -> 7856 bytes Resources/public/fonts/icomoon.woff | Bin 7840 -> 7932 bytes Resources/public/fonts/selection.json | 97 +- .../js/views/fields/ez-image-editview.js | 477 +++++++ .../public/templates/fields/edit/image.hbt | 76 ++ .../fields/assets/ez-image-editview-tests.js | 1102 +++++++++++++++++ Tests/js/views/fields/ez-image-editview.html | 77 ++ 16 files changed, 2009 insertions(+), 32 deletions(-) create mode 100644 Resources/public/css/theme/views/fields/edit/image.css create mode 100644 Resources/public/css/views/fields/edit/image.css create mode 100644 Resources/public/js/views/fields/ez-image-editview.js create mode 100644 Resources/public/templates/fields/edit/image.hbt create mode 100644 Tests/js/views/fields/assets/ez-image-editview-tests.js create mode 100644 Tests/js/views/fields/ez-image-editview.html diff --git a/Resources/config/css.yml b/Resources/config/css.yml index afcb5548e..ef2d074ac 100644 --- a/Resources/config/css.yml +++ b/Resources/config/css.yml @@ -42,6 +42,7 @@ system: - '@eZPlatformUIBundle/Resources/public/css/views/fields/edit/integer.css' - '@eZPlatformUIBundle/Resources/public/css/views/fields/edit/maplocation.css' - '@eZPlatformUIBundle/Resources/public/css/views/fields/edit/author.css' + - '@eZPlatformUIBundle/Resources/public/css/views/fields/edit/image.css' - '@eZPlatformUIBundle/Resources/public/css/views/fields/edit/selection.css' - '@eZPlatformUIBundle/Resources/public/css/modules/tabs.css' - '@eZPlatformUIBundle/Resources/public/css/modules/page-header.css' @@ -78,6 +79,7 @@ system: - '@eZPlatformUIBundle/Resources/public/css/theme/views/fields/edit/maplocation.css' - '@eZPlatformUIBundle/Resources/public/css/theme/views/fields/edit/author.css' - '@eZPlatformUIBundle/Resources/public/css/theme/views/fields/edit/selection.css' + - '@eZPlatformUIBundle/Resources/public/css/theme/views/fields/edit/image.css' - '@eZPlatformUIBundle/Resources/public/css/theme/modules/tabs.css' - '@eZPlatformUIBundle/Resources/public/css/theme/modules/page-header.css' - '@eZPlatformUIBundle/Resources/public/css/theme/modules/serverside-content.css' diff --git a/Resources/config/image_variations.yml b/Resources/config/image_variations.yml index f78155f7a..538955de0 100644 --- a/Resources/config/image_variations.yml +++ b/Resources/config/image_variations.yml @@ -5,3 +5,7 @@ system: reference: reference filters: - {name: geometry/scaleheightdownonly, params: [250]} + platformui_editview: + reference: reference + filters: + - {name: geometry/scalewidthdownonly, params: [300]} diff --git a/Resources/config/yui.yml b/Resources/config/yui.yml index 9038f3331..42cd15bb0 100644 --- a/Resources/config/yui.yml +++ b/Resources/config/yui.yml @@ -149,6 +149,7 @@ system: - 'ez-textline-editview' - 'ez-textblock-editview' - 'ez-xmltext-editview' + - 'ez-image-editview' - 'ez-emailaddress-editview' - 'ez-maplocation-editview' - 'ez-url-editview' @@ -226,6 +227,12 @@ system: xmltexteditview-ez-template: type: 'template' path: %ez_platformui.public_dir%/templates/fields/edit/xmltext.hbt + ez-image-editview: + requires: ['ez-fieldeditview', 'imageeditview-ez-template', 'ez-asynchronousview', 'event-tap'] + path: %ez_platformui.public_dir%/js/views/fields/ez-image-editview.js + imageeditview-ez-template: + type: 'template' + path: %ez_platformui.public_dir%/templates/fields/edit/image.hbt ez-emailaddress-editview: requires: ['ez-fieldeditview', 'event-valuechange', 'emailaddresseditview-ez-template'] path: %ez_platformui.public_dir%/js/views/fields/ez-emailaddress-editview.js diff --git a/Resources/public/css/modules/button.css b/Resources/public/css/modules/button.css index 5b282aa3d..a672ed0a4 100644 --- a/Resources/public/css/modules/button.css +++ b/Resources/public/css/modules/button.css @@ -7,6 +7,19 @@ padding: 0.4em 0.5em; } +.ez-button-height:before { + display: block; + font-size: 200%; + text-align: center; + margin-bottom: 0.2em; +} + +.ez-button-height { + text-align: center; + width: 14em; + white-space: normal; +} + .ez-button[data-icon]:before { padding-right: 0.5em; } diff --git a/Resources/public/css/theme/modules/button.css b/Resources/public/css/theme/modules/button.css index eb180e5c5..2105d8807 100644 --- a/Resources/public/css/theme/modules/button.css +++ b/Resources/public/css/theme/modules/button.css @@ -42,3 +42,8 @@ content: "\E615"; padding-right: 0.5em; } + +.ez-button-upload:before { + content: "\E61f"; + padding-right: 0.5em; +} diff --git a/Resources/public/css/theme/views/fields/edit/image.css b/Resources/public/css/theme/views/fields/edit/image.css new file mode 100644 index 000000000..1ab90bd7e --- /dev/null +++ b/Resources/public/css/theme/views/fields/edit/image.css @@ -0,0 +1,82 @@ +.ez-view-imageeditview .ez-image-empty { + color: #888; + font-style: italic; +} + +.ez-view-imageeditview .ez-image-view-original { + font-size: 80%; +} + +.ez-view-imageeditview .ez-image-view-original:after { + font-family: 'ez-platformui-icomoon'; + content: "\E610"; + padding-left: 0.3em; +} + +.ez-view-imageeditview .ez-image-properties-title { + font-size: 120%; + border-bottom: 1px solid #ccc; +} + +.ez-view-imageeditview .ez-image-properties-original { + list-style-type: square; +} + +.ez-view-imageeditview.is-error .ez-button-upload { + color: #BF3E33; + border: 1px solid #BF3E33; +} + +.ez-view-imageeditview .ez-image-warning { + border-radius: 3px; + background: #222; + opacity: 0.85; + color: #fff; + display: block; + padding: 0; + + -webkit-transform: scale(0); + -moz-transform: scale(0); + -ms-transform: scale(0); + -o-transform: scale(0); + transform: scale(0); + -webkit-transform-origin: center bottom; + -moz-transform-origin: center bottom; + -ms-transform-origin: center bottom; + -o-transform-origin: center bottom; + transform-origin: center bottom; + -webkit-transition: all 0.2s ease; + -moz-transition: all 0.2s ease; + -ms-transition: all 0.2s ease; + -o-transition: all 0.2s ease; + transition: all 0.2s ease; +} + +.ez-view-imageeditview.has-warning .ez-image-warning { + padding: 0.8em; + + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); +} + +.ez-view-imageeditview .ez-image-warning-hide { + font-size: 90%; +} + +.ez-view-imageeditview .ez-image-warning-hide:after { + content: "\E600"; + color: #fff; + display: inline-block; +} + +.ez-view-imageeditview .ez-image-warning-text:before { + content: "\E617"; + font-size: 300%; + padding-right: 0.3em; + color: #fff; + display: block; + float: left; +} diff --git a/Resources/public/css/views/fields/edit/image.css b/Resources/public/css/views/fields/edit/image.css new file mode 100644 index 000000000..c9f972771 --- /dev/null +++ b/Resources/public/css/views/fields/edit/image.css @@ -0,0 +1,98 @@ +.ez-view-imageeditview .ez-image-input-ui { + width: 100%; + display: inline-block; + vertical-align: top; +} + +.ez-view-imageeditview .ez-image-empty, +.ez-view-imageeditview .ez-asynchronousview-loading, +.ez-view-imageeditview .ez-asynchronousview-error { + margin: 0.5em 0 0 0.5em; +} + +.ez-view-imageeditview .ez-image-editpreview-image { + float: left; + max-width: 300px; +} + +.ez-view-imageeditview .ez-image-preview { + height: auto; + max-width: 100%; + display: block; +} + +.ez-view-imageeditview .ez-image-properties { + margin-left: 300px; + padding-left: 1em; +} + +.ez-view-imageeditview .ez-image-properties:after { + display: block; + background: #f00; + clear: both; + content: ""; +} + +.ez-view-imageeditview .ez-image-properties-title { + margin-top: 0; + padding: 0.3em 0.3em 0.3em 0; +} + +.ez-view-imageeditview .ez-image-properties-original { + margin: 0 0 0 1.2em; + padding: 0; + line-height: 1.4em; +} + +.ez-view-imageeditview .ez-image-alt-text { + margin-top: 1.5em; +} + +.ez-view-imageeditview .ez-image-alt-text-label { + display: block; + margin-bottom: 0.5em; +} + +.ez-view-imageeditview .ez-image-alt-text-input { + width: 100%; +} + +.ez-view-imageeditview .ez-image-remove { + margin: 0.5em 0 0 0; +} + +.ez-view-imageeditview .ez-image-action { + text-align: center; + position: relative; +} + +.ez-view-imageeditview .ez-image-input-file { + display: none; +} + +.ez-view-imageeditview .ez-image-action .ez-button { + margin: 1em; +} + +.ez-view-imageeditview .ez-image-warning { + display: none; + text-align: left; + position: absolute; + bottom: 100%; + left: 10%; + width: 80%; + padding: 0.8em; +} + +.ez-view-imageeditview.has-warning .ez-image-warning { + display: block; + } + +.ez-view-imageeditview .ez-image-warning-hide { + float: right; + display: block; +} + +.ez-view-imageeditview .ez-image-warning-text { + margin: 0; +} diff --git a/Resources/public/fonts/icomoon.eot b/Resources/public/fonts/icomoon.eot index d20d4cce6cf39bf3b66da3cb39e160f3652ae2b4..e470c1f04a931c190e5122fa9b0292269659a927 100644 GIT binary patch delta 363 zcmexid&Q15M4o|R!$ekdmMy=heVyDWD>5-8Sn<`dNPhsTGs^%9a5%A~GBC(!0Qo8zxg`}%4ea}Yd>B#{gu@g48SICFZ7vOD=l{$}q@H z0Le2jvrJBAbYPU9yoAx2QGW9a#z?7pCI*K82Ur-G8yMIagn+t?1&uiwjm1TI8Fd*= zSp`{D1sM-C9Qe=hpW(m(M%91O|Du6p0}}%i17iat10ztpAUSvL+}z{|U=<+SfTCdV zb=vPO@%%Pl8Ms-%{0qzHYJnIK8fa?+^MT1ZGWv`vljq3vOjeQg5#eNjuRY0tpG zSiry_rIV4Gn8NbHbU6cq^c + \ No newline at end of file diff --git a/Resources/public/fonts/icomoon.ttf b/Resources/public/fonts/icomoon.ttf index 8fe92e7248abdb5ef2467373fc1c413e89c2784d..a499a8ff4cfb9ea2b3451578580a363cb22190b2 100644 GIT binary patch delta 377 zcmca&v%$8WfsuiMft#U$ftkU;KUm+0ZxXX7P-G7fCnV=47D$DjDPv$@lmYU6(i4jd zfV2RRp8=#f(sL@)jJBMb3*-aEr5Z9)6H{2;)?H*^kp2LaH_HGDa5%A~GBC(!0Qo8z zxg`}%4ea}Yd>-P9iV_$Zem3NgB}BrEeq11ke8U78ZNo)9gyz< zw8XU_zqkbGP#}<-FmZ=Fqx>XBXGZzWE{u^<^-K&5{|~S*FgGx;F$e(_8w(n9G8&7E z@-pf&nz9PAstPh5XgKhn;XlKH1B|NwqW?t$$p$6{CI-d^MqnuXXDCR{ojW%-c>-7k z$P#ea0I9Fjes78AxB1Gz%>ov_uzao-hykI2wl**yn0!K7pHXG<59yxC4Kh9=ybeHl aP|&I{++yGa(hLktlO5%RH@}xjWds0*BwalK delta 267 zcmdmBd&Q=nfsuiMft#U$ftkU;KUm+0ZxXWyP-G7fCnV=47D$DjDP>?_lmYU6(i4jd zfV2RRp8=#f(sL@)HgDc(599;IrF1e<6H{1Tm@a2vke&mSH_HGDa5%A~0Qpyde3gvc zl8U+p_WeNq4{NxhJ0A<|_j?3z&al$&aZZ f28f<4B4f^|JlRL4XYw5x4>qvZl{f3jHZcMKn%_WI diff --git a/Resources/public/fonts/icomoon.woff b/Resources/public/fonts/icomoon.woff index 4343571827424b6dc0f6c37e290451d9ba157b9e..57951f2a3fc73250b6602be90bca55e2bceba2b8 100644 GIT binary patch delta 393 zcmZ2r`^Q$K+~3WOfsp|SbXEERgDEIqLpB(?>} z=YV2?^qk5xpx7P;2B`)RHrjG(ZboWi3Il_T4N#342(!GcyO;qK1d5da`6?jH;lz@f zky}y$6q^C$PXOVj2KN0q`N=?acCs2k0WT0v+!39Wn^*y~SQeyN0gUw+?&KxrrUJ#L z04;X~;c&@i?+WsZfokOLO#I`|C_mYU(V0`hW`gx7?>Lv*cgO>s*MGW zIT?+`MR^%@8BJLQSycrY4>TP3&+wn&zyU_pf6@P+!m0_X;yAz+f>>$Kln;`wd9GH|ni1uiU~s|8{}XrQeP%m*fm$mlbwOm>m!nS4gZ Shl|$%C=b%EvRO{Hi4g!!fL^Bn delta 301 zcmexkyTDeY+~3WOfsp|SIm0+Bg;4L00lfiIHV#vDL1hKXtAsWkgov7x(s*n z5_405VirKlT|qcpa@o6r{NfUzZyF~4@n@8q?8E5HD7Se6W2BTa*q00pVyc^a;`wd9 qGH|m1jbUK8u;j;77(F>b#+*@k@(h`t$r`dAY~X-V-drWy#0UV6)kIAI diff --git a/Resources/public/fonts/selection.json b/Resources/public/fonts/selection.json index f9ac11e0b..a28637fbc 100644 --- a/Resources/public/fonts/selection.json +++ b/Resources/public/fonts/selection.json @@ -1,6 +1,38 @@ { "IcoMoonType": "selection", "icons": [ + { + "icon": { + "paths": [ + "M448 576h128v-256h192l-256-256-256 256h192zM640 432v98.712l293.066 109.288-421.066 157.018-421.066-157.018 293.066-109.288v-98.712l-384 144v256l512 192 512-192v-256z" + ], + "attrs": [ + { + "visibility": false + } + ], + "tags": [ + "upload", + "load", + "arrow" + ], + "grid": 32 + }, + "attrs": [ + { + "visibility": false + } + ], + "properties": { + "order": 1, + "id": 1, + "prevSize": 32, + "code": 58911, + "name": "upload" + }, + "setIdx": 0, + "iconIdx": 0 + }, { "icon": { "paths": [ @@ -32,7 +64,7 @@ "name": "download" }, "setIdx": 0, - "iconIdx": 0 + "iconIdx": 1 }, { "icon": { @@ -52,7 +84,7 @@ "attrs": [], "properties": { "id": 120, - "order": 30, + "order": 31, "prevSize": 32, "code": 58908, "name": "spinner", @@ -80,7 +112,7 @@ "attrs": [], "properties": { "id": 2, - "order": 1, + "order": 2, "prevSize": 32, "code": 58880, "name": "close", @@ -110,7 +142,7 @@ "attrs": [], "properties": { "id": 3, - "order": 3, + "order": 4, "prevSize": 32, "code": 58881, "name": "file", @@ -137,7 +169,7 @@ "attrs": [], "properties": { "id": 4, - "order": 4, + "order": 5, "prevSize": 32, "code": 58882, "name": "arrow-down", @@ -164,7 +196,7 @@ "attrs": [], "properties": { "id": 5, - "order": 5, + "order": 6, "prevSize": 32, "code": 58883, "name": "arrow-up", @@ -191,7 +223,7 @@ "attrs": [], "properties": { "id": 6, - "order": 6, + "order": 7, "prevSize": 32, "code": 58884, "name": "arrow-left", @@ -218,7 +250,7 @@ "attrs": [], "properties": { "id": 7, - "order": 7, + "order": 8, "prevSize": 32, "code": 58885, "name": "arrow-right", @@ -246,7 +278,7 @@ "attrs": [], "properties": { "id": 8, - "order": 8, + "order": 9, "prevSize": 32, "code": 58886, "name": "pencil", @@ -271,7 +303,7 @@ "attrs": [], "properties": { "id": 9, - "order": 9, + "order": 10, "prevSize": 32, "code": 58887, "name": "eye", @@ -299,7 +331,7 @@ "attrs": [], "properties": { "id": 10, - "order": 10, + "order": 11, "prevSize": 32, "code": 58888, "name": "screen", @@ -324,7 +356,7 @@ "attrs": [], "properties": { "id": 11, - "order": 11, + "order": 12, "prevSize": 32, "code": 58889, "name": "tablet", @@ -350,7 +382,7 @@ "attrs": [], "properties": { "id": 12, - "order": 12, + "order": 13, "prevSize": 32, "code": 58890, "name": "mobile", @@ -378,7 +410,7 @@ "attrs": [], "properties": { "id": 13, - "order": 13, + "order": 14, "prevSize": 32, "code": 58891, "name": "bubbles", @@ -406,7 +438,7 @@ "attrs": [], "properties": { "id": 14, - "order": 14, + "order": 15, "prevSize": 32, "code": 58892, "name": "menu", @@ -434,7 +466,7 @@ "attrs": [], "properties": { "id": 15, - "order": 15, + "order": 16, "prevSize": 32, "code": 58893, "name": "rocket", @@ -461,7 +493,7 @@ "attrs": [], "properties": { "id": 16, - "order": 16, + "order": 17, "prevSize": 32, "code": 58894, "name": "floppy", @@ -486,7 +518,7 @@ "attrs": [], "properties": { "id": 17, - "order": 17, + "order": 18, "prevSize": 32, "code": 58895, "name": "refresh", @@ -514,7 +546,7 @@ "attrs": [], "properties": { "id": 346, - "order": 18, + "order": 19, "prevSize": 32, "code": 58896, "name": "new-tab", @@ -539,7 +571,7 @@ "attrs": [], "properties": { "id": 146, - "order": 19, + "order": 20, "prevSize": 32, "code": 58897, "name": "wand", @@ -566,7 +598,7 @@ "attrs": [], "properties": { "id": 150, - "order": 20, + "order": 21, "prevSize": 32, "code": 58898, "name": "stats", @@ -595,7 +627,7 @@ "attrs": [], "properties": { "id": 144, - "order": 21, + "order": 22, "prevSize": 32, "code": 58899, "name": "cog", @@ -620,7 +652,7 @@ "attrs": [], "properties": { "id": 286, - "order": 22, + "order": 23, "prevSize": 32, "code": 58900, "name": "shuffle", @@ -648,7 +680,7 @@ "attrs": [], "properties": { "id": 168, - "order": 23, + "order": 24, "prevSize": 32, "code": 58901, "name": "remove", @@ -673,7 +705,7 @@ "attrs": [], "properties": { "id": 258, - "order": 24, + "order": 25, "prevSize": 32, "code": 58902, "name": "plus", @@ -697,7 +729,7 @@ "attrs": [], "properties": { "id": 247, - "order": 25, + "order": 26, "prevSize": 32, "code": 58903, "name": "info", @@ -721,7 +753,7 @@ "attrs": [], "properties": { "id": 162, - "order": 26, + "order": 27, "prevSize": 32, "code": 58904, "name": "dashboard", @@ -746,7 +778,7 @@ "attrs": [], "properties": { "id": 185, - "order": 27, + "order": 28, "prevSize": 32, "code": 58905, "name": "tree", @@ -773,7 +805,7 @@ "attrs": [], "properties": { "id": 88, - "order": 28, + "order": 29, "prevSize": 32, "code": 58906, "name": "cabinet", @@ -797,7 +829,7 @@ "attrs": [], "properties": { "id": 244, - "order": 29, + "order": 30, "prevSize": 32, "code": 58907, "name": "warning", @@ -820,7 +852,7 @@ }, "properties": { "id": 258, - "order": 2, + "order": 3, "prevSize": 32, "code": 58909, "name": "plus2" @@ -857,7 +889,8 @@ "columns": 16, "margin": 16, "png": false, - "sprites": true + "sprites": true, + "prefix": "icon-" }, "historySize": 100, "showCodes": true, diff --git a/Resources/public/js/views/fields/ez-image-editview.js b/Resources/public/js/views/fields/ez-image-editview.js new file mode 100644 index 000000000..30d590565 --- /dev/null +++ b/Resources/public/js/views/fields/ez-image-editview.js @@ -0,0 +1,477 @@ +/* + * Copyright (C) eZ Systems AS. All rights reserved. + * For full copyright and license information view LICENSE file distributed with this source code. + */ +YUI.add('ez-image-editview', function (Y) { + "use strict"; + /** + * Provides the field edit view for the Image (ezimage) fields + * + * @module ez-image-editview + */ + + Y.namespace('eZ'); + + var FIELDTYPE_IDENTIFIER = 'ezimage', + HAS_WARNING = 'has-warning', + L = Y.Lang, + win = Y.config.win, + events = { + '.ez-image-input-file': { + 'change': '_updateImage' + }, + '.ez-button-upload': { + 'tap': '_chooseImage', + }, + '.ez-button-delete': { + 'tap': '_removeImage', + }, + '.ez-image-alt-text-input': { + 'valuechange': '_altTextChange', + 'blur': 'validate', + }, + '.ez-image-warning-hide': { + 'tap': '_hideWarning', + }, + }; + + /** + * The Image edit view + * + * @namespace eZ + * @class ImageEditView + * @constructor + * @extends eZ.FieldEditView + */ + Y.eZ.ImageEditView = Y.Base.create('imageEditView', Y.eZ.FieldEditView, [Y.eZ.AsynchronousView], { + initializer: function () { + this.events = Y.merge(this.events, events); + this._fireMethod = this._fireLoadImageVariation; + this._watchAttribute = false; + + this._initAttributesData(); + this._initAttributesEvents(); + }, + + /** + * Initializes the inital attribute values depending on the passed field + * during the building of the object + * + * @method _initAttributesData + * @protected + */ + _initAttributesData: function () { + var fieldValue = this.get('field').fieldValue; + + this._set('image', this.get('field')); + if ( fieldValue ) { + this._set('alternativeText', fieldValue.alternativeText); + } + }, + + /** + * Initializes the attributes events handling + * + * @method _initAttributesEvents + * @protected + */ + _initAttributesEvents: function () { + this.after('warningChange', this._uiHandleWarningMessage); + + this.after('imageVariationChange', function (e) { + this.get('image').displayUri = e.newVal.uri; + this.render(); + }); + + this.after('imageChange', function () { + this._set('updated', true); + this.render(); + this.validate(); + }); + this.after('alternativeTextChange', function () { + this._set('updated', true); + }); + }, + + /** + * Event handler for the valuechange event on the input field for the + * alternative text + * + * @method _altTextChange + * @protected + * @param {EventFacade} e event facade of the valuechange event + */ + _altTextChange: function (e) { + this._set('alternativeText', e.target.get('value')); + }, + + /** + * Event handler for the tap event on the upload button + * + * @method _chooseImage + * @protected + * @param {EventFacade} e event facade of the tap event + */ + _chooseImage: function (e) { + e.preventDefault(); + this._set('warning', false); + this.get('container').one('.ez-image-input-file').getDOMNode().click(); + }, + + /** + * Event handler for the change event on the file input + * + * @method _updateImage + * @protected + * @param {EventFacade} e event facade of the change event + */ + _updateImage: function (e) { + var file = e.target.getDOMNode().files[0], + that = this, + reader, msg; + + if ( this._validSize(file.size) ) { + reader = this.get('fileReader'); + reader.onload = function (e) { + var base64 = reader.result.replace(/^.*;base64,/, ''); + that._set('image', that._createFileStruct(file, base64)); + reader.onload = undefined; + }; + reader.readAsDataURL(file); + } else { + e.target.set('value', ''); + msg = "The file '" + file.name + "' was refused because"; + msg += ' its size is greater than the maximum allowed size ('; + msg += this.get('fieldDefinition').validatorConfiguration.FileSizeValidator.maxFileSize; + msg += ' bytes).'; + this._set('warning', msg); + } + }, + + /** + * Checks whether the size is valid according to the field definition + * configuration + * + * @param {Number} size + * @method _validSize + * @return {Boolean} + * @private + */ + _validSize: function (size) { + var maxSize = this.get('fieldDefinition').validatorConfiguration.FileSizeValidator.maxFileSize; + + if ( maxSize ) { + return (size < maxSize); + } + return true; + }, + + /** + * Event handler for the tap event on the remove button + * + * @method _removeImage + * @protected + * @param {EventFacade} e event facade of tap event + */ + _removeImage: function (e) { + e.preventDefault(); + this._set('warning', false); + this._set('image', null); + }, + + /** + * warningChange event handler, it displays/hides the warning message + * depending on the attribute value. + * + * @method _uiHandleWarningMessage + * @protected + */ + _uiHandleWarningMessage: function () { + var warning = this.get('warning'), + container = this.get('container'); + + if ( !warning ) { + container.removeClass(HAS_WARNING); + } else { + container.one('.ez-image-warning-text').setContent(warning); + container.addClass(HAS_WARNING); + } + }, + + /** + * Event handler for the tap event on the hide link of the warning box. + * + * @param {EventFacade} e tap event facade + * @method _hideWarning + * @protected + */ + _hideWarning: function (e) { + e.preventDefault(); + this._set('warning', false); + }, + + /** + * Validates the current input of the image against the is required + * field definition setting. + * + * @method validate + */ + validate: function () { + var def = this.get('fieldDefinition'); + + if ( def.isRequired && this._isEmpty() ) { + this.set('errorStatus', 'This field is required'); + } else { + this.set('errorStatus', false); + } + }, + + /** + * Defines the variables to be imported in the field edit template. + * + * @protected + * @method _variables + * @return {Object} + */ + _variables: function () { + return { + "isRequired": this.get('fieldDefinition').isRequired, + "isEmpty": this._isEmpty(), + "image": this.get('image'), + "alternativeText": this.get('alternativeText'), + "loadingError": this.get('loadingError'), + }; + }, + + /** + * Checks whether the image field is currently empty. + * + * @protected + * @method _isEmpty + * @return {Boolean} + */ + _isEmpty: function () { + return !this.get('image'); + }, + + /** + * Returns the field value suitable for the REST API based on the + * current input. It makes to sure to only send the actual image content + * when it's needed. + * + * @method _getFieldValue + * @protected + * @return {Object} + */ + _getFieldValue: function () { + var image = this.get('image'), + that = this, + fieldValue; + + if ( !this.get('updated') ) { + return undefined; + } + + if ( !image ) { + return null; + } + + this.get('version').onceAfter('save', function (e) { + // this is to make sure we don't send again and again the + // image in the REST requests + that._set('updated', false); + }); + + fieldValue = { + fileName: image.name, + fileSize: image.size, + alternativeText: this.get('alternativeText'), + }; + if ( image.data ) { + fieldValue.data = image.data; + } + return fieldValue; + }, + + /** + * Fire the `loadImageVariation` event + * + * @method _fireLoadImageVariation + * @protected + */ + _fireLoadImageVariation: function () { + if ( this.get('field').fieldValue ) { + /** + * Fired when the view needs the image variation + * + * @event loadImageVariation + * @param {Object} field the Image field + * @param {String} variation the variation name (large, + * reference, ...) + */ + this.fire('loadImageVariation', { + field: this.get('field'), + variation: this.get('variationIdentifier'), + }); + } + }, + + /** + * Creates the image structure based on the File object provided by the + * input file and on the base64 encoded image content. It also creates a + * blob URL for the newly selected object. + * + * @method _createFileStruct + * @param {File} file + * @param {String} content base64 encoded image content + * @return {Object} + * @protected + */ + _createFileStruct: function (file, content) { + var url = win.URL.createObjectURL(file); + + return { + name: file.name, + type: file.type, + size: file.size, + originalUri: url, + displayUri: url, + data: content, + }; + }, + + /** + * image attribute setter. It converts the different input type to a + * consistent object no matter if the image attribute is filled from the + * REST fieldValue or from the user input. + * + * @protected + * @method _imageSetter + * @param {Object|Null} value + * @return {Object} + */ + _imageSetter: function (value) { + var file, + previousValue = this.get('image'); + + if ( previousValue ) { + win.URL.revokeObjectURL(previousValue.displayUri); + } + if ( value === null ) { + return null; + } else if ( L.isObject(value) && !L.isUndefined(value.fieldValue) ) { + file = value.fieldValue; + if ( file === null ) { + return null; + } + return { + name: file.fileName, + type: null, // missing in the REST API, see https://jira.ez.no/browse/EZP-23758 + size: file.fileSize, + originalUri: file.uri, + // displayUri value will be set after the asynchronous + // loading of the variation + displayUri: false, + }; + } else if ( L.isObject(value) ) { + return value; + } + return Y.Attribute.INVALID_VALUE; + }, + }, { + ATTRS: { + /** + * The image struct object for the current field. This attribute has + * a setter to accept either null value, any REST Image fieldValue + * or an object created from a File. + * + * @readOnly + * @attribute image + * @type {Object|null} + */ + image: { + readOnly: true, + setter: '_imageSetter', + value: null, + }, + + /** + * The alternative image text + * + * @attribute alternativeText + * @readOnly + * @type {String} + */ + alternativeText: { + readOnly: true, + value: "", + }, + + /** + * Flag indicating whether the user changed something in the image + * field. This attribute is used to avoid sending the same image + * again and again. + * + * @attribute updated + * @readOnly + * @type {Boolean} + */ + updated: { + readOnly: true, + value: false, + }, + + /** + * Stores the warning message (if any) or false + * + * @attribute warning + * @readOnly + * @type {String|false} + */ + warning: { + readOnly: true, + value: false + }, + + /** + * FileReader instance + * + * @attribute fileReader + * @type FileReader + * @readOnly + */ + fileReader: { + readOnly: true, + valueFn: function () { + return new FileReader(); + }, + }, + + /** + * The image variation to display + * + * @attribute imageVariation + * @type {Object} + */ + imageVariation: { + value: null, + }, + + /** + * The variation identifier to use to display the image + * + * @attribute variationIdentifier + * @type {String} + * @default 'platformui_rawcontentview' + * @initOnly + */ + variationIdentifier: { + value: 'platformui_editview' + } + }, + }); + + Y.eZ.FieldEditView.registerFieldEditView( + FIELDTYPE_IDENTIFIER, Y.eZ.ImageEditView + ); +}); diff --git a/Resources/public/templates/fields/edit/image.hbt b/Resources/public/templates/fields/edit/image.hbt new file mode 100644 index 000000000..0cc189517 --- /dev/null +++ b/Resources/public/templates/fields/edit/image.hbt @@ -0,0 +1,76 @@ +
+
+ {{> ez_fieldinfo_tooltip }} + +
+
+
+ {{#if image.displayUri}} +
+
+ {{ image.alternativeText }} +
+
+

+ Original image properties + (View the full width image) +

+
    +
  • File name: {{ image.name }}
  • + {{#if image.type}} +
  • Type: {{ image.type }}
  • + {{/if}} +
  • File size: {{ image.size }} bytes
  • +
+ +
+ + +
+
+
+ {{/if}} + {{#if isEmpty}} +

No image yet.

+ {{else}} + {{#if loadingError}} +

+ An error occurred while loading the image. + +

+ {{else}} + {{#unless image.displayUri}} +

Loading the current image...

+ {{/unless}} + {{/if}} + {{/if}} +
+
+ +

+
+ + or + +
+ +
+
+
diff --git a/Tests/js/views/fields/assets/ez-image-editview-tests.js b/Tests/js/views/fields/assets/ez-image-editview-tests.js new file mode 100644 index 000000000..797636185 --- /dev/null +++ b/Tests/js/views/fields/assets/ez-image-editview-tests.js @@ -0,0 +1,1102 @@ +/* + * Copyright (C) eZ Systems AS. All rights reserved. + * For full copyright and license information view LICENSE file distributed with this source code. + */ +YUI.add('ez-image-editview-tests', function (Y) { + var viewTest, registerTest, imageSetterTest, alternativeTextTest, + imageVariationTest, buttonsTest, warningTest, + validateTest, pickImageTest, + getFieldNotUpdatedTest, getFieldUpdatedEmptyTest, + getFieldUpdatedTest, getFieldUpdatedNoDataTest, + Assert = Y.Assert, Mock = Y.Mock; + + viewTest = new Y.Test.Case({ + name: "eZ Image View test", + + _getFieldDefinition: function (required) { + return { + isRequired: required + }; + }, + + setUp: function () { + this.field = {fieldValue: null}; + this.jsonContent = {}; + this.jsonContentType = {}; + this.jsonVersion = {}; + this.content = new Mock(); + this.version = new Mock(); + this.contentType = new Mock(); + Mock.expect(this.content, { + method: 'toJSON', + returns: this.jsonContent + }); + Mock.expect(this.version, { + method: 'toJSON', + returns: this.jsonVersion + }); + Mock.expect(this.contentType, { + method: 'toJSON', + returns: this.jsonContentType + }); + }, + + _initView: function () { + this.view = new Y.eZ.ImageEditView({ + container: '.container', + field: this.field, + version: this.version, + content: this.content, + contentType: this.contentType + }); + }, + + tearDown: function () { + this.view.destroy(); + }, + + _testAvailableVariables: function (required, expectRequired, expectedIsEmpty) { + var fieldDefinition = this._getFieldDefinition(required), + view, + that = this; + + this._initView(); + view = this.view; + view.set('fieldDefinition', fieldDefinition); + view.template = function (variables) { + Assert.isObject(variables, "The template should receive some variables"); + Assert.areEqual(10, Y.Object.keys(variables).length, "The template should receive 10 variables"); + + Assert.areSame( + that.jsonContent, variables.content, + "The content should be available in the field edit view template" + ); + Assert.areSame( + that.jsonVersion, variables.version, + "The version should be available in the field edit view template" + ); + Assert.areSame( + that.jsonContentType, variables.contentType, + "The contentType should be available in the field edit view template" + ); + Assert.areSame( + fieldDefinition, variables.fieldDefinition, + "The fieldDefinition should be available in the field edit view template" + ); + Assert.areSame( + that.field, variables.field, + "The field should be available in the field edit view template" + ); + Assert.areSame( + expectedIsEmpty, + variables.isEmpty, + "isEmpty should be available in the field edit view template" + ); + Assert.areSame( + view.get('image'), variables.image, + "The image struct should be available in the field edit view template" + ); + Assert.areSame( + view.get('alternativeText'), variables.alternativeText, + "The alternativeText should be available in the field edit view template" + ); + Assert.areSame( + view.get('loadingError'), variables.loadingError, + "The loadingError should be available in the field edit view template" + ); + + Assert.areSame(expectRequired, variables.isRequired); + + return ''; + }; + this.view.render(); + }, + + "Test not required field": function () { + this._testAvailableVariables(false, false, true); + }, + + "Test required field": function () { + this._testAvailableVariables(true, true, true); + }, + + "Test filled image": function () { + this.field = { + fieldValue: { + fileName: 'troll.jpg', + fileSize: 42, + originalUri: '/path/to/troll.jpg', + alternativeText: "Vilain Troll", + } + }; + + this._testAvailableVariables(false, false, false); + }, + }); + + imageSetterTest = new Y.Test.Case({ + name: "eZ Image View image attribute setter test", + + setUp: function () { + var that = this, + win = Y.config.win; + + this.field = {fieldValue: null}; + this.fieldDefinition = {isRequired: false}; + this.content = new Mock(); + this.version = new Mock(); + this.contentType = new Mock(); + Mock.expect(this.content, { + method: 'toJSON', + returns: {} + }); + Mock.expect(this.version, { + method: 'toJSON', + returns: {} + }); + Mock.expect(this.contentType, { + method: 'toJSON', + returns: {} + }); + this.revokeCalled = false; + this.revokeUri = ""; + + this.originalURL = win.URL; + win.URL = {}; + win.URL.revokeObjectURL = function (uri) { + that.revokeCalled = true; + that.revokeUri = uri; + // phantomjs seems to not support window.URL + if ( that.originalURL && that.originalURL.revokeObjectURL ) { + that.originalURL.revokeObjectURL(uri); + } + }; + }, + + _initView: function () { + this.view = new Y.eZ.ImageEditView({ + container: '.container', + field: this.field, + fieldDefinition: this.fieldDefinition, + version: this.version, + content: this.content, + contentType: this.contentType + }); + }, + + tearDown: function () { + this.view.destroy(); + Y.config.win.URL = this.originalURL; + }, + + "Should handle a null field value": function () { + this._initView(); + Assert.isNull(this.view.get('image'), 'The image attr should be null'); + Assert.isFalse(this.revokeCalled, "revokeObjectURL should not have been called"); + }, + + "Should handle a null value": function () { + this._initView(); + this.view._set('image', null); + Assert.isNull(this.view.get('image'), 'The image attr should be null'); + Assert.isFalse(this.revokeCalled, "revokeObjectURL should not have been called"); + }, + + "Should handle a field value": function () { + var image; + + this.field = { + fieldValue: { + uri: 'path/to/file.jpg', + fileName: 'file.jpg', + fileSize: 42, + } + }; + this._initView(); + + image = this.view.get('image'); + Assert.isObject(image, 'The image attr should be an object'); + Assert.areEqual( + this.field.fieldValue.uri, + image.originalUri, + "The image object should get a originalUri property" + ); + Assert.areEqual( + this.field.fieldValue.fileName, + image.name, + "The image object should get a name property" + ); + Assert.areEqual( + this.field.fieldValue.fileSize, + image.size, + "The image object should get a size property" + ); + Assert.isNull(image.type, "The type property should be null"); + Assert.isFalse(image.displayUri, "The displayUri property should be false"); + Assert.isFalse(this.revokeCalled, "revokeObjectURL should not have been called"); + }, + + "Should handle a literal object": function () { + var image, + value = { + originalUri: 'path/to/file.jpg', + displayUri: 'path/to/file.jpg', + name: 'file.jpg', + size: 42, + type: 'image/jpg', + data: "base64content", + }; + + this._initView(); + this.view._set('image', value); + + image = this.view.get('image'); + Assert.areSame(value, image, "The image attr should store the object"); + Assert.isFalse(this.revokeCalled, "revokeObjectURL should not have been called"); + }, + + "Should reject unrecognized value": function () { + this._initView(); + this.view._set('image', undefined); + Assert.isNull(this.view.get('image'), "The type property should be null"); + Assert.isFalse(this.revokeCalled, "revokeObjectURL should not have been called"); + }, + + "Should revoke the displayUri of the previous entry": function () { + var value = { + originalUri: 'path/to/file.jpg', + displayUri: 'path/to/file.jpg', + name: 'file.jpg', + size: 42, + type: 'image/jpg', + data: "base64content", + }, + value2 = { + originalUri: 'path/to/file.jpg', + displayUri: 'path/to/file.jpg', + name: 'file.jpg', + size: 42, + type: 'image/jpg', + data: "base64content", + }; + + + this._initView(); + this.view._set('image', value); + this.view._set('image', value2); + + Assert.isTrue(this.revokeCalled, "revokeObjectURL should have been called"); + Assert.areEqual( + value.displayUri, this.revokeUri, + "The previous displayUri should have been revoked" + ); + }, + }); + + alternativeTextTest = new Y.Test.Case({ + name: "eZ Image View image alternative text", + + setUp: function () { + this.field = { + fieldValue: { + alternativeText: "default alternative text", + uri: "path/to/file.jpg" + }, + }; + this.fieldDefinition = {isRequired: false}; + this.content = new Mock(); + this.version = new Mock(); + this.contentType = new Mock(); + Mock.expect(this.content, { + method: 'toJSON', + returns: {} + }); + Mock.expect(this.version, { + method: 'toJSON', + returns: {} + }); + Mock.expect(this.contentType, { + method: 'toJSON', + returns: {} + }); + + this.view = new Y.eZ.ImageEditView({ + container: '.container', + field: this.field, + fieldDefinition: this.fieldDefinition, + version: this.version, + content: this.content, + contentType: this.contentType + }); + }, + + tearDown: function () { + this.view.destroy(); + }, + + "Should retrieve the alternative text in the field value": function () { + Assert.areEqual( + this.field.fieldValue.alternativeText, + this.view.get('alternativeText'), + "The fieldValue alternativeText should be stored in the alternativeText attribute" + ); + }, + + "Should track the change in the alternative text input": function () { + var container = this.view.get('container'), + newAlt = 'new alternative', + that = this, + input; + + this.view.render(); + this.view.after('alternativeTextChange', function () { + that.resume(function () { + Assert.areEqual( + newAlt, this.view.get('alternativeText'), + "The alternativeText attribute should have been updated" + ); + Assert.isTrue( + this.view.get('updated'), + "The updated flag attribute should be true" + ); + }); + }); + input = container.one('.ez-image-alt-text-input'); + input.simulate('focus'); + input.set('value', newAlt); + this.wait(); + }, + }); + + imageVariationTest = new Y.Test.Case({ + name: "eZ Image View image variation test", + + setUp: function () { + this.variationIdentifier = 'reference'; + this.field = { + fieldValue: { + alternativeText: "default alternative text", + uri: "path/to/file.jpg" + }, + }; + this.fieldDefinition = {isRequired: false}; + this.content = new Mock(); + this.version = new Mock(); + this.contentType = new Mock(); + Mock.expect(this.content, { + method: 'toJSON', + returns: {} + }); + Mock.expect(this.version, { + method: 'toJSON', + returns: {} + }); + Mock.expect(this.contentType, { + method: 'toJSON', + returns: {} + }); + + this.view = new Y.eZ.ImageEditView({ + container: '.container', + field: this.field, + fieldDefinition: this.fieldDefinition, + version: this.version, + content: this.content, + contentType: this.contentType, + variationIdentifier: this.variationIdentifier, + }); + }, + + tearDown: function () { + this.view.destroy(); + }, + + "Should fire the loadImageVariation event": function () { + var loadEvent = false, + that = this; + + this.view.on('loadImageVariation', function (e) { + loadEvent = true; + Assert.areSame( + that.field, + e.field, + "The field should be provided in the event facade" + ); + Assert.areEqual( + that.variationIdentifier, + e.variation, + "The variation identifier should be provided in the event facade" + ); + }); + this.view.set('active', true); + + Assert.isTrue(loadEvent, "The loadImageVariation event should have been fired"); + }, + + "Should not fire the loadImageVariation event": function () { + this.field.fieldValue = null; + this.view.on('loadImageVariation', function (e) { + Assert.fail('The loadImageVariation should not have been fired'); + }); + this.view.set('active', true); + }, + + "Should render the view when the imageVariation attribute changes": function () { + var imageVariation = { + uri: 'uri/to/the/variation', + }, + templateCalled = false, + origTpl = this.view.template; + + this.view.template = function (variables) { + templateCalled = true; + Assert.areEqual( + imageVariation.uri, + this.get('image').displayUri, + "The displayUri property should have been updated with the variation" + ); + return origTpl.apply(this, arguments); + }; + this.view.set('imageVariation', imageVariation); + + Assert.isTrue(templateCalled, "The template has not been used"); + }, + + "Should render the view when the loadingError attribute changes": function () { + var that = this, + templateCalled = false, + origTpl = this.view.template; + + this.view.template = function (variables) { + templateCalled = true; + Assert.areSame( + that.view.get('loadingError'), + variables.loadingError, + "loadingError should be available in the template" + ); + return origTpl.apply(this, arguments); + }; + + this.view.set('loadingError', true); + + Assert.isTrue(templateCalled, "The template has not been used"); + }, + + "Should try to reload the image when tapping on the retry button": function () { + var that = this, + loadImageVariation = false; + + this.view.render(); + this.view.set('active', true); + this.view.set('loadingError', true); + this.view.on('loadImageVariation', function () { + loadImageVariation = true; + }); + + this.view.get('container').one('.ez-asynchronousview-retry').simulateGesture('tap', function () { + that.resume(function () { + Assert.isTrue( + loadImageVariation, + "The loadImageVariation should have been fired" + ); + Assert.isFalse( + this.view.get('loadingError'), + "The `loadingError` attribute should be resetted to false" + ); + }); + }); + this.wait(); + }, + }); + + buttonsTest = new Y.Test.Case({ + name: "eZ Image View image buttons test", + + setUp: function () { + var win = Y.config.win, + that = this; + + this.field = { + fieldValue: { + alternativeText: "default alternative text", + uri: "path/to/file.jpg" + }, + }; + this.fieldDefinition = {isRequired: false}; + this.content = new Mock(); + this.version = new Mock(); + this.contentType = new Mock(); + Mock.expect(this.content, { + method: 'toJSON', + returns: {} + }); + Mock.expect(this.version, { + method: 'toJSON', + returns: {} + }); + Mock.expect(this.contentType, { + method: 'toJSON', + returns: {} + }); + + this.originalURL = win.URL; + win.URL = {}; + win.URL.revokeObjectURL = function (uri) { + // phantomjs seems to not support window.URL + if ( that.originalURL && that.originalURL.revokeObjectURL ) { + that.originalURL.revokeObjectURL(uri); + } + }; + + this.view = new Y.eZ.ImageEditView({ + container: '.container', + field: this.field, + fieldDefinition: this.fieldDefinition, + version: this.version, + content: this.content, + contentType: this.contentType + }); + }, + + tearDown: function () { + this.view.destroy(); + Y.config.win.URL = this.originalURL; + }, + + "Should remove the image": function () { + var container = this.view.get('container'), + that = this; + + this.view.render(); + + this.view._set('warning', "Test warning"); + container.one('.ez-button-delete').simulateGesture('tap', function () { + that.resume(function () { + Assert.isFalse( + this.view.get('warning'), "The warning should be set to false" + ); + Assert.isNull( + this.view.get('image'), "The image attribute should be null" + ); + }); + }); + this.wait(); + }, + + "Should open the select file window": function () { + var container = this.view.get('container'), + that = this; + + this.view.render(); + + container.one('.ez-image-input-file').on('click', function () { + that.resume(function () { + Assert.isFalse( + this.view.get('warning'), "The warning should be set to false" + ); + }); + }); + + this.view._set('warning', "Test warning"); + container.one('.ez-button-upload').simulateGesture('tap'); + this.wait(); + }, + }); + + warningTest = new Y.Test.Case({ + name: "eZ Image View image warning test", + + setUp: function () { + this.field = { + fieldValue: null + }; + this.fieldDefinition = {isRequired: false}; + this.content = new Mock(); + this.version = new Mock(); + this.contentType = new Mock(); + Mock.expect(this.content, { + method: 'toJSON', + returns: {} + }); + Mock.expect(this.version, { + method: 'toJSON', + returns: {} + }); + Mock.expect(this.contentType, { + method: 'toJSON', + returns: {} + }); + + this.view = new Y.eZ.ImageEditView({ + container: '.container', + field: this.field, + fieldDefinition: this.fieldDefinition, + version: this.version, + content: this.content, + contentType: this.contentType + }); + }, + + tearDown: function () { + this.view.destroy(); + }, + + "Should show the warning box": function () { + var text = 'Et hop!', + container = this.view.get('container'); + + this.view.render(); + this.view._set('warning', text); + Assert.isTrue( + container.hasClass('has-warning'), + "The container should get the has-warning class" + ); + }, + + "Should hide the warning box": function () { + var text = 'Et hop!', + that = this, + container = this.view.get('container'); + + this.view.render(); + this.view._set('warning', text); + + container.one('.ez-image-warning-hide').simulateGesture('tap', function () { + that.resume(function () { + Assert.isFalse( + container.hasClass('has-warning'), + "The container should not have the has-warning class" + ); + Assert.isFalse( + this.view.get('warning'), + "The warning should be set to false" + ); + }); + }); + this.wait(); + } + }); + + validateTest = new Y.Test.Case({ + name: "eZ Image View image validate test", + + setUp: function () { + this.field = { + fieldValue: null + }; + this.fieldDefinition = {isRequired: true}; + this.content = new Mock(); + this.version = new Mock(); + this.contentType = new Mock(); + Mock.expect(this.content, { + method: 'toJSON', + returns: {} + }); + Mock.expect(this.version, { + method: 'toJSON', + returns: {} + }); + Mock.expect(this.contentType, { + method: 'toJSON', + returns: {} + }); + }, + + _initView: function () { + this.view = new Y.eZ.ImageEditView({ + container: '.container', + field: this.field, + fieldDefinition: this.fieldDefinition, + version: this.version, + content: this.content, + contentType: this.contentType + }); + this.view.render(); + }, + + tearDown: function () { + this.view.destroy(); + }, + + "Test not required empty": function () { + this.fieldDefinition.isRequired = false; + this.field.fieldValue = null; + this._initView(); + this.view.validate(); + + Assert.isTrue( + this.view.isValid(), + "An empty value is valid" + ); + }, + + "Test required empty": function () { + this.fieldDefinition.isRequired = true; + this.field.fieldValue = null; + this._initView(); + this.view.validate(); + + Assert.isFalse( + this.view.isValid(), + "An empty value is invalid" + ); + }, + + "Test not required not empty": function () { + this.fieldDefinition.isRequired = false; + this.field.fieldValue = { + uri: "path/to/file.jpg", + fileName: "file.jpg", + }; + this._initView(); + this.view.validate(); + + Assert.isTrue( + this.view.isValid(), + "A not empty value is valid" + ); + }, + + "Test required not empty": function () { + this.fieldDefinition.isRequired = true; + this.field.fieldValue = { + uri: "path/to/file.jpg", + fileName: "file.jpg", + }; + this._initView(); + this.view.validate(); + + Assert.isTrue( + this.view.isValid(), + "A not empty value is valid" + ); + }, + }); + + pickImageTest = new Y.Test.Case({ + name: "eZ Image View image pick image test", + + setUp: function () { + var that = this, win = Y.config.win; + + this.field = { + fieldValue: null + }; + this.maxSize = 10; + this.fieldDefinition = { + isRequired: false, + validatorConfiguration: { + FileSizeValidator: { + maxFileSize: this.maxSize + } + } + }; + this.content = new Mock(); + this.version = new Mock(); + this.contentType = new Mock(); + Mock.expect(this.content, { + method: 'toJSON', + returns: {} + }); + Mock.expect(this.version, { + method: 'toJSON', + returns: {} + }); + Mock.expect(this.contentType, { + method: 'toJSON', + returns: {} + }); + + this.view = new Y.eZ.ImageEditView({ + container: '.container', + field: this.field, + fieldDefinition: this.fieldDefinition, + version: this.version, + content: this.content, + contentType: this.contentType + }); + this.view._set('fileReader', new Mock()); + this.originalURL = win.URL; + win.URL = {}; + win.URL.createObjectURL = function (uri) { + // phantomjs seems to not support window.URL + if ( that.originalURL && that.originalURL.createObjectURL ) { + that.originalURL.revokeObjectURL(uri); + } + }; + }, + + tearDown: function () { + this.view.destroy(); + Y.config.win.URL = this.originalURL; + }, + + // PhantomJS is not able to programmatically set the value of an input + // file element, so we are forced to directly call the protected change + // event handler. Those tests are inspired by the Y.Uploader tests, see + // https://github.com/yui/yui3/blob/master/src/uploader/tests/unit/uploaderhtml5.html + "Should refuse the file": function () { + var fileReader = this.view.get('fileReader'), + eventFacade = new Y.DOMEventFacade({ + type: 'change' + }); + + eventFacade.target = new Mock(); + Mock.expect(fileReader, { + method: 'readAsDataURL', + callCount: 0, + }); + Mock.expect(eventFacade.target, { + method: 'getDOMNode', + returns: {files: [{size: 50, name: "file.jpg", type: "image/jpg"}]}, + }); + Mock.expect(eventFacade.target, { + method: 'set', + args: ['value', ''], + }); + + this.view.render(); + this.view._updateImage(eventFacade); + Assert.isString( + this.view.get('warning'), + "A warning should have been generated" + ); + Mock.verify(eventFacade); + Mock.verify(fileReader); + }, + + "Should read the file": function () { + var fileReader = this.view.get('fileReader'), + imgContent = "base64 image content", + file = {size: 5, name: "file.jpg", type: "image/jpg"}, + base64ImgContent = "data;" + file.type + ";base64," + imgContent, + image, + eventFacade = new Y.DOMEventFacade({ + type: 'change' + }); + + eventFacade.target = new Mock(); + Mock.expect(eventFacade.target, { + method: 'getDOMNode', + returns: {files: [file]}, + }); + Mock.expect(fileReader, { + method: 'readAsDataURL', + args: [file], + run: function (f) { + fileReader.result = base64ImgContent; + fileReader.onload(); + } + }); + + this.view.render(); + this.view._updateImage(eventFacade); + Assert.isFalse( + this.view.get('warning'), + "The warning attribute should stay false" + ); + image = this.view.get('image'); + + Assert.areEqual( + imgContent, image.data, + "The image content should be available in the image attribute" + ); + Assert.areEqual( + file.name, image.name, + "The image name should be available in the image attribute" + ); + Assert.areEqual( + file.size, image.size, + "The image size should be available in the image attribute" + ); + Assert.areEqual( + file.type, image.type, + "The image type should be available in the image attribute" + ); + Assert.isUndefined( + fileReader.onload, + "The onload handler should be resetted" + ); + }, + + "Should read the file (no max size)": function () { + this.view.get('fieldDefinition').validatorConfiguration.FileSizeValidator.maxFileSize = false; + this["Should read the file"](); + } + }); + + getFieldNotUpdatedTest = new Y.Test.Case( + Y.merge(Y.eZ.Test.GetFieldTests, { + _should: { + ignore: { + "Test getField": true, + } + }, + fieldDefinition: {isRequired: false}, + fieldValue: null, + ViewConstructor: Y.eZ.ImageEditView, + + _setNewValue: function () { + + }, + + "Should return undefined": function () { + this.view.render(); + this._setNewValue(); + + Assert.isUndefined( + this.view.getField(), + "getField should return undefined" + ); + } + }) + ); + + getFieldUpdatedEmptyTest = new Y.Test.Case( + Y.merge(Y.eZ.Test.GetFieldTests, { + fieldDefinition: {isRequired: false}, + fieldValue: null, + newValue: null, + ViewConstructor: Y.eZ.ImageEditView, + + _setNewValue: function () { + this.view._set('updated', true); + }, + }) + ); + + getFieldUpdatedTest = new Y.Test.Case( + Y.merge(Y.eZ.Test.GetFieldTests, { + version: new Y.Model(), + fieldDefinition: {isRequired: false}, + fieldValue: { + fileName: "original.jpg", + alternativeText: "Alt text", + }, + newValue: { + name: "me.jpg", + size: "42", + data: "base64 content", + }, + ViewConstructor: Y.eZ.ImageEditView, + + _afterSetup: function () { + var that = this, + win = Y.config.win; + + this.originalURL = win.URL; + win.URL = {}; + win.URL.revokeObjectURL = function (uri) { + // phantomjs seems to not support window.URL + if ( that.originalURL && that.originalURL.revokeObjectURL ) { + that.originalURL.revokeObjectURL(uri); + } + }; + }, + + _afterTearnDown: function () { + Y.config.win.URL = this.originalURL; + }, + + _setNewValue: function () { + this.view._set('image', this.newValue); + }, + + _assertCorrectFieldValue: function (fieldValue, msg) { + Assert.areEqual(this.newValue.name, fieldValue.fileName, msg); + Assert.areEqual(this.newValue.size, fieldValue.fileSize, msg); + Assert.areEqual(this.newValue.data, fieldValue.data, msg); + Assert.areEqual(this.fieldValue.alternativeText, fieldValue.alternativeText, msg); + }, + + "Should reset the updated attribute after version save": function () { + this.view.render(); + this._setNewValue(); + + this.view.getField(); + + Assert.isTrue(this.view.get('updated'), "The updated attribute should be true"); + this.version.fire('save'); + Assert.isFalse(this.view.get('updated'), "The updated attribute should be false"); + }, + }) + ); + + getFieldUpdatedNoDataTest = new Y.Test.Case( + Y.merge(Y.eZ.Test.GetFieldTests, { + version: new Y.Model(), + fieldDefinition: {isRequired: false}, + fieldValue: { + fileName: "original.jpg", + alternativeText: "Alt text", + }, + newValue: { + name: "me.jpg", + size: "42", + }, + ViewConstructor: Y.eZ.ImageEditView, + + _afterSetup: function () { + var that = this, + win = Y.config.win; + + this.originalURL = win.URL; + win.URL = {}; + win.URL.revokeObjectURL = function (uri) { + // phantomjs seems to not support window.URL + if ( that.originalURL && that.originalURL.revokeObjectURL ) { + that.originalURL.revokeObjectURL(uri); + } + }; + }, + + _afterTearnDown: function () { + Y.config.win.URL = this.originalURL; + }, + + _setNewValue: function () { + this.view._set('image', this.newValue); + }, + + _assertCorrectFieldValue: function (fieldValue, msg) { + Assert.areEqual(this.newValue.name, fieldValue.fileName, msg); + Assert.areEqual(this.newValue.size, fieldValue.fileSize, msg); + Assert.areEqual(this.fieldValue.alternativeText, fieldValue.alternativeText, msg); + }, + + "Should reset the updated attribute after version save": function () { + this.view.render(); + this._setNewValue(); + + this.view.getField(); + + Assert.isTrue(this.view.get('updated'), "The updated attribute should be true"); + this.version.fire('save'); + Assert.isFalse(this.view.get('updated'), "The updated attribute should be false"); + }, + }) + ); + + + Y.Test.Runner.setName("eZ Image Edit View tests"); + Y.Test.Runner.add(viewTest); + Y.Test.Runner.add(imageSetterTest); + Y.Test.Runner.add(alternativeTextTest); + Y.Test.Runner.add(imageVariationTest); + Y.Test.Runner.add(buttonsTest); + Y.Test.Runner.add(warningTest); + Y.Test.Runner.add(validateTest); + Y.Test.Runner.add(pickImageTest); + Y.Test.Runner.add(getFieldNotUpdatedTest); + Y.Test.Runner.add(getFieldUpdatedEmptyTest); + Y.Test.Runner.add(getFieldUpdatedTest); + Y.Test.Runner.add(getFieldUpdatedNoDataTest); + + registerTest = new Y.Test.Case(Y.eZ.EditViewRegisterTest); + registerTest.name = "Image Edit View registration test"; + registerTest.viewType = Y.eZ.ImageEditView; + registerTest.viewKey = "ezimage"; + + Y.Test.Runner.add(registerTest); + +}, '', {requires: ['test', 'model', 'event-valuechange', 'node-event-simulate', 'getfield-tests', 'editviewregister-tests', 'ez-image-editview']}); diff --git a/Tests/js/views/fields/ez-image-editview.html b/Tests/js/views/fields/ez-image-editview.html new file mode 100644 index 000000000..9fe65bac3 --- /dev/null +++ b/Tests/js/views/fields/ez-image-editview.html @@ -0,0 +1,77 @@ + + + +eZ Image Edit view tests + + + +
+ + + + + + + + + + + From 654a38e1c09e3cbb5eba62e8838d72ecf6d64767 Mon Sep 17 00:00:00 2001 From: Damien Pobel Date: Fri, 12 Dec 2014 12:18:44 +0100 Subject: [PATCH 07/13] Minor CS fixes --- Resources/public/js/extensions/ez-asynchronousview.js | 2 +- .../js/views/services/plugins/ez-imagevariationloadplugin.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/public/js/extensions/ez-asynchronousview.js b/Resources/public/js/extensions/ez-asynchronousview.js index d8464dfcb..a55f54509 100644 --- a/Resources/public/js/extensions/ez-asynchronousview.js +++ b/Resources/public/js/extensions/ez-asynchronousview.js @@ -42,7 +42,7 @@ YUI.add('ez-asynchronousview', function (Y) { * @required * @type {String} */ - + events: { '.ez-asynchronousview-retry': { 'tap': '_retryLoading', diff --git a/Resources/public/js/views/services/plugins/ez-imagevariationloadplugin.js b/Resources/public/js/views/services/plugins/ez-imagevariationloadplugin.js index 403ece4c0..c66aef87f 100644 --- a/Resources/public/js/views/services/plugins/ez-imagevariationloadplugin.js +++ b/Resources/public/js/views/services/plugins/ez-imagevariationloadplugin.js @@ -13,7 +13,7 @@ YUI.add('ez-imagevariationloadplugin', function (Y) { /** * Image variation load plugin. It sets an event handler on the - * loadImageVariation event to load an image variation + * loadImageVariation event to load an image variation * * @namespace eZ.Plugin * @class ImageVariationLoad From 4c57747ee8f4f55930368edf6fdde0224aff7ac8 Mon Sep 17 00:00:00 2001 From: Damien Pobel Date: Fri, 12 Dec 2014 17:52:34 +0100 Subject: [PATCH 08/13] EZP-23748: Improve the CSS to better handle small images --- .../public/css/views/fields/edit/image.css | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Resources/public/css/views/fields/edit/image.css b/Resources/public/css/views/fields/edit/image.css index c9f972771..8b06f6210 100644 --- a/Resources/public/css/views/fields/edit/image.css +++ b/Resources/public/css/views/fields/edit/image.css @@ -10,9 +10,11 @@ margin: 0.5em 0 0 0.5em; } -.ez-view-imageeditview .ez-image-editpreview-image { - float: left; - max-width: 300px; +.ez-view-imageeditview .ez-image-editpreview { + display: -webkit-flex; + display: flex; + -webkit-flex-direction: row; + flex-direction: row; } .ez-view-imageeditview .ez-image-preview { @@ -21,16 +23,13 @@ display: block; } -.ez-view-imageeditview .ez-image-properties { - margin-left: 300px; - padding-left: 1em; +.ez-view-imageeditview .ez-image-editpreview-image { + max-width: 300px; } -.ez-view-imageeditview .ez-image-properties:after { - display: block; - background: #f00; - clear: both; - content: ""; +.ez-view-imageeditview .ez-image-properties { + margin-left: 1em; + flex-grow: 1; } .ez-view-imageeditview .ez-image-properties-title { From 03dd18d9a2bcf0395d7e0b4edfa87f81b05d5065 Mon Sep 17 00:00:00 2001 From: Damien Pobel Date: Mon, 15 Dec 2014 13:47:16 +0100 Subject: [PATCH 09/13] EZP-23748: Make sure to always reset the file input --- Resources/public/js/views/fields/ez-image-editview.js | 2 +- Tests/js/views/fields/assets/ez-image-editview-tests.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Resources/public/js/views/fields/ez-image-editview.js b/Resources/public/js/views/fields/ez-image-editview.js index 30d590565..be4b09a5e 100644 --- a/Resources/public/js/views/fields/ez-image-editview.js +++ b/Resources/public/js/views/fields/ez-image-editview.js @@ -139,13 +139,13 @@ YUI.add('ez-image-editview', function (Y) { }; reader.readAsDataURL(file); } else { - e.target.set('value', ''); msg = "The file '" + file.name + "' was refused because"; msg += ' its size is greater than the maximum allowed size ('; msg += this.get('fieldDefinition').validatorConfiguration.FileSizeValidator.maxFileSize; msg += ' bytes).'; this._set('warning', msg); } + e.target.set('value', ''); }, /** diff --git a/Tests/js/views/fields/assets/ez-image-editview-tests.js b/Tests/js/views/fields/assets/ez-image-editview-tests.js index 797636185..d0480c45e 100644 --- a/Tests/js/views/fields/assets/ez-image-editview-tests.js +++ b/Tests/js/views/fields/assets/ez-image-editview-tests.js @@ -880,6 +880,10 @@ YUI.add('ez-image-editview-tests', function (Y) { method: 'getDOMNode', returns: {files: [file]}, }); + Mock.expect(eventFacade.target, { + method: 'set', + args: ['value', ''], + }); Mock.expect(fileReader, { method: 'readAsDataURL', args: [file], From 03fc28addd5597a9b371d6b0d7d7e5d28b5adeba Mon Sep 17 00:00:00 2001 From: Damien Pobel Date: Mon, 15 Dec 2014 13:48:51 +0100 Subject: [PATCH 10/13] EZP-23748: Always display a type entry in the image properties --- Resources/public/js/views/fields/ez-image-editview.js | 2 +- Tests/js/views/fields/assets/ez-image-editview-tests.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/public/js/views/fields/ez-image-editview.js b/Resources/public/js/views/fields/ez-image-editview.js index be4b09a5e..f4b5d60a1 100644 --- a/Resources/public/js/views/fields/ez-image-editview.js +++ b/Resources/public/js/views/fields/ez-image-editview.js @@ -366,7 +366,7 @@ YUI.add('ez-image-editview', function (Y) { } return { name: file.fileName, - type: null, // missing in the REST API, see https://jira.ez.no/browse/EZP-23758 + type: "N/A", // missing in the REST API, see https://jira.ez.no/browse/EZP-23758 size: file.fileSize, originalUri: file.uri, // displayUri value will be set after the asynchronous diff --git a/Tests/js/views/fields/assets/ez-image-editview-tests.js b/Tests/js/views/fields/assets/ez-image-editview-tests.js index d0480c45e..8aa5d6e0a 100644 --- a/Tests/js/views/fields/assets/ez-image-editview-tests.js +++ b/Tests/js/views/fields/assets/ez-image-editview-tests.js @@ -231,7 +231,7 @@ YUI.add('ez-image-editview-tests', function (Y) { image.size, "The image object should get a size property" ); - Assert.isNull(image.type, "The type property should be null"); + Assert.areEqual('N/A', image.type, "The type property should be 'N/A'"); Assert.isFalse(image.displayUri, "The displayUri property should be false"); Assert.isFalse(this.revokeCalled, "revokeObjectURL should not have been called"); }, From 7d4a1846cd5f1c59b681354f3b963912e31976b2 Mon Sep 17 00:00:00 2001 From: Damien Pobel Date: Mon, 15 Dec 2014 13:49:48 +0100 Subject: [PATCH 11/13] EZP-23748: Changed the image edit view to update itself instead of rerendering when the image is changed. This is to allow smoother transitions and graphical effects. --- .../css/theme/views/fields/edit/image.css | 108 +++++++-- .../public/css/views/fields/edit/image.css | 68 +++++- .../js/views/fields/ez-image-editview.js | 78 ++++++- .../public/templates/fields/edit/image.hbt | 68 +++--- .../fields/assets/ez-image-editview-tests.js | 207 ++++++++++++++++-- Tests/js/views/fields/ez-image-editview.html | 7 + 6 files changed, 461 insertions(+), 75 deletions(-) diff --git a/Resources/public/css/theme/views/fields/edit/image.css b/Resources/public/css/theme/views/fields/edit/image.css index 1ab90bd7e..598328b5b 100644 --- a/Resources/public/css/theme/views/fields/edit/image.css +++ b/Resources/public/css/theme/views/fields/edit/image.css @@ -1,6 +1,87 @@ +@-webkit-keyframes ez-image-expand { + 0% { + -webkit-transform: scaleY(0); + } + 100% { + -webkit-transform: scaleY(1); + } +} + +@-webkit-keyframes ez-image-collpase { + 0% { + -webkit-transform: scaleY(1); + max-height: 100%; + } + 100% { + -webkit-transform: scaleY(0); + max-height: 0; + } +} + +@keyframes ez-image-expand { + 0% { + transform: scaleY(0); + } + 100% { + transform: scaleY(1); + } +} + +@keyframes ez-image-collpase { + 0% { + transform: scaleY(1); + max-height: 100%; + } + 100% { + transform: scaleY(0); + max-height: 0; + } +} + +.ez-view-imageeditview .ez-image-editpreview { + -webkit-transform-origin: center top; + transform-origin: center top; + -webkit-animation: ez-image-expand 0.2s ease forwards; + animation: ez-image-expand 0.2s ease forwards; +} + +.ez-view-imageeditview.is-image-empty .ez-image-editpreview { + display: -webkit-flex; + display: flex; + -webkit-animation: ez-image-collpase 0.2s ease forwards; + animation: ez-image-collpase 0.2s ease forwards; +} + +.ez-view-imageeditview.is-image-being-updated .ez-image-preview { + opacity: 0.1; + -webkit-transform: scale(0); + transform: scale(0); +} + +.ez-view-imageeditview .ez-image-preview { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1); + -webkit-transform-origin: left top; + transform-origin: left top; + -webkit-transition: all 0.1s ease; + transition: all 0.1s ease; +} + +.ez-view-imageeditview.is-image-loading .ez-asynchronousview-loading, +.ez-view-imageeditview.is-image-loading .ez-asynchronousview-error { + font-size: 120%; +} + .ez-view-imageeditview .ez-image-empty { color: #888; font-style: italic; + -webkit-transition: all 0.2s ease; + transition: all 0.2s ease; +} + +.ez-view-imageeditview.is-error .ez-image-empty { + color: #BF3E33; } .ez-view-imageeditview .ez-image-view-original { @@ -22,9 +103,18 @@ list-style-type: square; } -.ez-view-imageeditview.is-error .ez-button-upload { - color: #BF3E33; - border: 1px solid #BF3E33; +.ez-view-imageeditview .ez-image-remove-option { + opacity: 1; + -webkit-transition: all 0.2s ease; + transition: all 0.2s ease; +} + +.ez-view-imageeditview.is-image-empty .ez-image-remove-option { + opacity: 0.4; +} + +.ez-view-imageeditview.is-image-empty .ez-button-delete[disabled] { + opacity: 1; } .ez-view-imageeditview .ez-image-warning { @@ -36,19 +126,10 @@ padding: 0; -webkit-transform: scale(0); - -moz-transform: scale(0); - -ms-transform: scale(0); - -o-transform: scale(0); transform: scale(0); -webkit-transform-origin: center bottom; - -moz-transform-origin: center bottom; - -ms-transform-origin: center bottom; - -o-transform-origin: center bottom; transform-origin: center bottom; -webkit-transition: all 0.2s ease; - -moz-transition: all 0.2s ease; - -ms-transition: all 0.2s ease; - -o-transition: all 0.2s ease; transition: all 0.2s ease; } @@ -56,9 +137,6 @@ padding: 0.8em; -webkit-transform: scale(1); - -moz-transform: scale(1); - -ms-transform: scale(1); - -o-transform: scale(1); transform: scale(1); } diff --git a/Resources/public/css/views/fields/edit/image.css b/Resources/public/css/views/fields/edit/image.css index 8b06f6210..22cf569a7 100644 --- a/Resources/public/css/views/fields/edit/image.css +++ b/Resources/public/css/views/fields/edit/image.css @@ -4,12 +4,72 @@ vertical-align: top; } -.ez-view-imageeditview .ez-image-empty, -.ez-view-imageeditview .ez-asynchronousview-loading, +.ez-view-imageeditview.is-image-empty .ez-image-editpreview { + display: none; +} + +.ez-view-imageeditview .ez-image-empty { + display: none; +} + +.ez-view-imageeditview.is-image-empty .ez-image-empty { + display: block; +} + +.ez-view-imageeditview .ez-image-upload-new { + display: none; +} + +.ez-view-imageeditview .ez-image-upload-replace { + display: inline; +} + +.ez-view-imageeditview.is-image-empty .ez-image-upload-new { + display: inline; +} + +.ez-view-imageeditview.is-image-empty .ez-image-upload-replace { + display: none; +} + +.ez-view-imageeditview .ez-asynchronousview-loading { + display: none; +} + +.ez-view-imageeditview.is-image-loading .ez-image-preview { + display: none; +} + +.ez-view-imageeditview.is-image-loading .ez-asynchronousview-loading { + display: block; +} + .ez-view-imageeditview .ez-asynchronousview-error { + display: none; +} + +.ez-view-imageeditview.has-loading-error .ez-asynchronousview-loading { + display: none; +} + +.ez-view-imageeditview.has-loading-error .ez-asynchronousview-error { + display: block; +} + +.ez-view-imageeditview .ez-asynchronousview-retry { + display: block; + margin: 0.5em auto; +} + +.ez-view-imageeditview .ez-image-empty { margin: 0.5em 0 0 0.5em; } +.ez-view-imageeditview .ez-asynchronousview-loading, +.ez-view-imageeditview .ez-asynchronousview-error { + margin: 1em 0; +} + .ez-view-imageeditview .ez-image-editpreview { display: -webkit-flex; display: flex; @@ -29,7 +89,9 @@ .ez-view-imageeditview .ez-image-properties { margin-left: 1em; - flex-grow: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; } .ez-view-imageeditview .ez-image-properties-title { diff --git a/Resources/public/js/views/fields/ez-image-editview.js b/Resources/public/js/views/fields/ez-image-editview.js index f4b5d60a1..449751f1c 100644 --- a/Resources/public/js/views/fields/ez-image-editview.js +++ b/Resources/public/js/views/fields/ez-image-editview.js @@ -14,6 +14,10 @@ YUI.add('ez-image-editview', function (Y) { var FIELDTYPE_IDENTIFIER = 'ezimage', HAS_WARNING = 'has-warning', + IS_EMPTY = 'is-image-empty', + IS_LOADING = 'is-image-loading', + IS_BEING_UPDATED = 'is-image-being-updated', + HAS_LOADING_ERROR = 'has-loading-error', L = Y.Lang, win = Y.config.win, events = { @@ -80,19 +84,86 @@ YUI.add('ez-image-editview', function (Y) { this.after('imageVariationChange', function (e) { this.get('image').displayUri = e.newVal.uri; - this.render(); + this._uiImageChange(); }); this.after('imageChange', function () { this._set('updated', true); - this.render(); - this.validate(); + this._uiImageChange(); }); this.after('alternativeTextChange', function () { this._set('updated', true); }); }, + /** + * Reflects the new image object in the generated UI + * + * @method _uiImageChange + * @protected + */ + _uiImageChange: function () { + var image = this.get('image'), + container = this.get('container'), + removeButton = container.one('.ez-button-delete'), + imgNode = container.one('.ez-image-preview'); + + if ( image ) { + container.one('.ez-image-properties-name').setContent(image.name); + container.one('.ez-image-properties-size').setContent(image.size); + container.one('.ez-image-properties-type').setContent(image.type); + container.one('.ez-image-view-original').setAttribute('href', image.originalUri); + removeButton.set('disabled', false); + if ( image.displayUri ) { + imgNode.setAttribute('src', image.displayUri); + container.removeClass(IS_BEING_UPDATED); + } + } else { + // no need to update the DOM, the image is hidden by CSS + removeButton.set('disabled', true); + } + this._setStateClasses(); + this.validate(); + }, + + render: function () { + this.constructor.superclass.render.call(this); + this._setStateClasses(); + return this; + }, + + /** + * Toggle a classe on the view container based on the value + * + * @method _toggleClass + * @param Mixed value + * @param {String} cl the class to toggle + * @private + */ + _toggleClass: function (value, cl) { + var container = this.get('container'); + + if ( value ) { + container.addClass(cl); + } else { + container.removeClass(cl); + } + }, + + /** + * Set the state classes on the view container + * + * @method _setStateClasses + * @protected + */ + _setStateClasses: function () { + var isEmpty = this._isEmpty(); + + this._toggleClass(isEmpty, IS_EMPTY); + this._toggleClass(!this.get('updated') && !isEmpty && !this.get('imageVariation'), IS_LOADING); + this._toggleClass(this.get('loadingError'), HAS_LOADING_ERROR); + }, + /** * Event handler for the valuechange event on the input field for the * alternative text @@ -131,6 +202,7 @@ YUI.add('ez-image-editview', function (Y) { reader, msg; if ( this._validSize(file.size) ) { + this.get('container').addClass(IS_BEING_UPDATED); reader = this.get('fileReader'); reader.onload = function (e) { var base64 = reader.result.replace(/^.*;base64,/, ''); diff --git a/Resources/public/templates/fields/edit/image.hbt b/Resources/public/templates/fields/edit/image.hbt index 0cc189517..e1ad9143b 100644 --- a/Resources/public/templates/fields/edit/image.hbt +++ b/Resources/public/templates/fields/edit/image.hbt @@ -10,10 +10,14 @@
- {{#if image.displayUri}}
- {{ image.alternativeText }} + {{ image.alternativeText }} +

Loading a thumbnail of the image...

+

+ An error occurred while loading the thumbnail. + +

@@ -21,11 +25,9 @@ (View the full width image)

    -
  • File name: {{ image.name }}
  • - {{#if image.type}} -
  • Type: {{ image.type }}
  • - {{/if}} -
  • File size: {{ image.size }} bytes
  • +
  • File name: {{ image.name }}
  • +
  • Type: {{ image.type }}
  • +
  • File size: {{ image.size }} bytes
@@ -37,40 +39,28 @@
- {{/if}} - {{#if isEmpty}}

No image yet.

- {{else}} - {{#if loadingError}} -

- An error occurred while loading the image. - -

- {{else}} - {{#unless image.displayUri}} -

Loading the current image...

- {{/unless}} - {{/if}} - {{/if}} -
-
- -

+
+
+ +

+
+ + + or + +
- - or - -
-
+
diff --git a/Tests/js/views/fields/assets/ez-image-editview-tests.js b/Tests/js/views/fields/assets/ez-image-editview-tests.js index 8aa5d6e0a..9078353f6 100644 --- a/Tests/js/views/fields/assets/ez-image-editview-tests.js +++ b/Tests/js/views/fields/assets/ez-image-editview-tests.js @@ -4,7 +4,7 @@ */ YUI.add('ez-image-editview-tests', function (Y) { var viewTest, registerTest, imageSetterTest, alternativeTextTest, - imageVariationTest, buttonsTest, warningTest, + imageVariationTest, buttonsTest, warningTest, renderingTest, validateTest, pickImageTest, getFieldNotUpdatedTest, getFieldUpdatedEmptyTest, getFieldUpdatedTest, getFieldUpdatedNoDataTest, @@ -182,6 +182,7 @@ YUI.add('ez-image-editview-tests', function (Y) { content: this.content, contentType: this.contentType }); + this.view.render(); }, tearDown: function () { @@ -441,25 +442,23 @@ YUI.add('ez-image-editview-tests', function (Y) { this.view.set('active', true); }, - "Should render the view when the imageVariation attribute changes": function () { + "Should update the view when the imageVariation attribute changes": function () { var imageVariation = { uri: 'uri/to/the/variation', - }, - templateCalled = false, - origTpl = this.view.template; + }; - this.view.template = function (variables) { - templateCalled = true; - Assert.areEqual( - imageVariation.uri, - this.get('image').displayUri, - "The displayUri property should have been updated with the variation" - ); - return origTpl.apply(this, arguments); - }; + this.view.render(); this.view.set('imageVariation', imageVariation); - Assert.isTrue(templateCalled, "The template has not been used"); + Assert.areEqual( + imageVariation.uri, this.view.get('image').displayUri, + "The variation uri should be set as the display Uri" + ); + Assert.areEqual( + imageVariation.uri, + this.view.get('container').one('.ez-image-preview').getAttribute('src'), + "The variation should be displayed" + ); }, "Should render the view when the loadingError attribute changes": function () { @@ -867,6 +866,7 @@ YUI.add('ez-image-editview-tests', function (Y) { "Should read the file": function () { var fileReader = this.view.get('fileReader'), + container = this.view.get('container'), imgContent = "base64 image content", file = {size: 5, name: "file.jpg", type: "image/jpg"}, base64ImgContent = "data;" + file.type + ";base64," + imgContent, @@ -888,6 +888,10 @@ YUI.add('ez-image-editview-tests', function (Y) { method: 'readAsDataURL', args: [file], run: function (f) { + Assert.isTrue( + container.hasClass('is-image-being-updated'), + "The container should get the is-image-being-updated class" + ); fileReader.result = base64ImgContent; fileReader.onload(); } @@ -1081,6 +1085,178 @@ YUI.add('ez-image-editview-tests', function (Y) { }) ); + renderingTest = new Y.Test.Case({ + name: "eZ Image View image rendering tests", + + setUp: function () { + var win = Y.config.win, that = this; + + this.field = {}; + this.fieldDefinition = {isRequired: false}; + this.content = new Mock(); + this.version = new Mock(); + this.contentType = new Mock(); + Mock.expect(this.content, { + method: 'toJSON', + returns: {} + }); + Mock.expect(this.version, { + method: 'toJSON', + returns: {} + }); + Mock.expect(this.contentType, { + method: 'toJSON', + returns: {} + }); + + this.originalURL = win.URL; + win.URL = {}; + win.URL.revokeObjectURL = function (uri) { + // phantomjs seems to not support window.URL + if ( that.originalURL && that.originalURL.revokeObjectURL ) { + that.originalURL.revokeObjectURL(uri); + } + }; + this.createdUrl = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; + }, + + _initView: function () { + this.view = new Y.eZ.ImageEditView({ + container: '.container', + field: this.field, + fieldDefinition: this.fieldDefinition, + version: this.version, + content: this.content, + contentType: this.contentType + }); + }, + + tearDown: function () { + Y.config.win.URL = this.originalURL; + this.view.destroy(); + }, + + "Should add the empty class": function () { + this.field = {fieldValue: null}; + this._initView(); + this.view.render(); + + Assert.isTrue( + this.view.get('container').hasClass('is-image-empty'), + "The container should get the is-image-empty class" + ); + }, + + "Should remove the empty class": function () { + this["Should add the empty class"](); + this.view._set('image', {name: "file.jpg", uri: "path/to/file.jpg"}); + + Assert.isFalse( + this.view.get('container').hasClass('is-image-empty'), + "The container should not get the is-image-empty class" + ); + }, + + "Should add the loading error class": function () { + this._initView(); + this.view.render(); + this.view.set('loadingError', true); + + Assert.isTrue( + this.view.get('container').hasClass('has-loading-error'), + "The container should get the has-loading-error class" + ); + }, + + "Should remove the loading error class": function () { + this["Should add the loading error class"](); + this.view.set('loadingError', false); + + Assert.isFalse( + this.view.get('container').hasClass('has-loading-error'), + "The container should not get the has-loading-error class" + ); + }, + + "Should add the loading class": function () { + this.field = {fieldValue: {name: "file.jpg"}}; + this._initView(); + this.view.render(); + + Assert.isTrue( + this.view.get('container').hasClass('is-image-loading'), + "The container should get the is-image-loading class" + ); + }, + + "Should remove the loading class": function () { + this["Should add the loading class"](); + this.view.set('imageVariation', {uri: "path/to/variation.jpg"}); + + Assert.isFalse( + this.view.get('container').hasClass('is-image-loading'), + "The container should not get the is-image-loading class" + ); + }, + + "Should disable the remove button": function () { + this.field = {fieldValue: {name: "file.jpg"}}; + this._initView(); + this.view.render(); + + this.view._set('image', null); + + Assert.isTrue( + this.view.get('container').one('.ez-button-delete').get('disabled'), + "The remove button should be disabled" + ); + }, + + "Should update the rendered view": function () { + var c, + newImage = { + name: "update.jpg", + size: 42, + type: "image/jpg", + originalUri: this.createdUrl, + displayUri: this.createdUrl, + }; + + this["Should disable the remove button"](); + c = this.view.get('container'); + + this.view._set('image', newImage); + + Assert.isFalse( + this.view.get('container').one('.ez-button-delete').get('disabled'), + "The remove button should be disabled" + ); + Assert.areEqual( + newImage.name, c.one(".ez-image-properties-name").getContent(), + "The image name should be updated" + ); + Assert.areEqual( + newImage.size, c.one(".ez-image-properties-size").getContent(), + "The image size should be updated" + ); + Assert.areEqual( + newImage.type, c.one(".ez-image-properties-type").getContent(), + "The image type should be updated" + ); + Assert.areEqual( + this.createdUrl, c.one(".ez-image-view-original").getAttribute('href'), + "The view original link should be updated" + ); + Assert.areEqual( + this.createdUrl, c.one(".ez-image-preview").getAttribute('src'), + "The view original link should be updated" + ); + Assert.isFalse( + c.hasClass('is-image-being-updated'), + "The container should not have the is-image-being-updated class" + ); + }, + }); Y.Test.Runner.setName("eZ Image Edit View tests"); Y.Test.Runner.add(viewTest); @@ -1091,6 +1267,7 @@ YUI.add('ez-image-editview-tests', function (Y) { Y.Test.Runner.add(warningTest); Y.Test.Runner.add(validateTest); Y.Test.Runner.add(pickImageTest); + Y.Test.Runner.add(renderingTest); Y.Test.Runner.add(getFieldNotUpdatedTest); Y.Test.Runner.add(getFieldUpdatedEmptyTest); Y.Test.Runner.add(getFieldUpdatedTest); diff --git a/Tests/js/views/fields/ez-image-editview.html b/Tests/js/views/fields/ez-image-editview.html index 9fe65bac3..fa2dafaa8 100644 --- a/Tests/js/views/fields/ez-image-editview.html +++ b/Tests/js/views/fields/ez-image-editview.html @@ -12,6 +12,13 @@ + +
Hide From acea3745184662b11d08801ba129fd767ff8eef0 Mon Sep 17 00:00:00 2001 From: Damien Pobel Date: Mon, 15 Dec 2014 13:54:31 +0100 Subject: [PATCH 12/13] Fixed: the validate() method of all field edit views is called on form validation This is to make sure all the field edit views are visiually updated if needed even if the user did not reach the end of the form or interact with all the edit views. --- .../public/js/views/ez-contenteditformview.js | 7 +++-- .../assets/ez-contenteditformview-tests.js | 26 +++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/Resources/public/js/views/ez-contenteditformview.js b/Resources/public/js/views/ez-contenteditformview.js index 741e4e294..05874cba5 100644 --- a/Resources/public/js/views/ez-contenteditformview.js +++ b/Resources/public/js/views/ez-contenteditformview.js @@ -109,10 +109,13 @@ YUI.add('ez-contenteditformview', function (Y) { * @return Boolean */ isValid: function () { - return Y.Array.every(this._fieldEditViews, function (view) { + var valid = true; + + Y.Array.each(this._fieldEditViews, function (view) { view.validate(); - return view.isValid(); + valid = valid && view.isValid(); }); + return valid; }, /** diff --git a/Tests/js/views/assets/ez-contenteditformview-tests.js b/Tests/js/views/assets/ez-contenteditformview-tests.js index da9037c8b..5b721d436 100644 --- a/Tests/js/views/assets/ez-contenteditformview-tests.js +++ b/Tests/js/views/assets/ez-contenteditformview-tests.js @@ -193,15 +193,21 @@ YUI.add('ez-contenteditformview-tests', function (Y) { } } }); + that.test1ValidateCalled = false; + that.test2ValidateCalled = false; Y.eZ.FieldEditView.registerFieldEditView('test1', Y.Base.create('fieldEdit1', Y.eZ.FieldEditView, [], { - validate: function () { }, + validate: function () { + that.test1ValidateCalled = true; + }, isValid: function () { return that.test1Valid; } })); Y.eZ.FieldEditView.registerFieldEditView('test2', Y.Base.create('fieldEdit2', Y.eZ.FieldEditView, [], { - validate: function () { }, + validate: function () { + that.test2ValidateCalled = true; + }, isValid: function () { return that.test2Valid; @@ -236,6 +242,10 @@ YUI.add('ez-contenteditformview-tests', function (Y) { this.test2Valid = true; Y.Assert.isTrue(this.view.isValid(), "The form validity should be true"); + Y.Assert.isTrue( + this.test1ValidateCalled && this.test2ValidateCalled, + "The validate() of all views should have been called" + ); }, "Should return the validity of the form (2)": function () { @@ -243,6 +253,10 @@ YUI.add('ez-contenteditformview-tests', function (Y) { this.test2Valid = false; Y.Assert.isFalse(this.view.isValid(), "The form validity should be false"); + Y.Assert.isTrue( + this.test1ValidateCalled && this.test2ValidateCalled, + "The validate() of all views should have been called" + ); }, "Should return the validity of the form (3)": function () { @@ -250,6 +264,10 @@ YUI.add('ez-contenteditformview-tests', function (Y) { this.test2Valid = true; Y.Assert.isFalse(this.view.isValid(), "The form validity should be false"); + Y.Assert.isTrue( + this.test1ValidateCalled && this.test2ValidateCalled, + "The validate() of all views should have been called" + ); }, "Should return the validity of the form (4)": function () { @@ -257,6 +275,10 @@ YUI.add('ez-contenteditformview-tests', function (Y) { this.test2Valid = false; Y.Assert.isFalse(this.view.isValid(), "The form validity should be false"); + Y.Assert.isTrue( + this.test1ValidateCalled && this.test2ValidateCalled, + "The validate() of all views should have been called" + ); }, }); From d2bfa0e54568f3ff8951260cb1c7892a6b144140 Mon Sep 17 00:00:00 2001 From: Damien Pobel Date: Mon, 15 Dec 2014 14:18:40 +0100 Subject: [PATCH 13/13] EZP-23748: s/Original image properties/Image properties/ in the template --- Resources/public/templates/fields/edit/image.hbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/public/templates/fields/edit/image.hbt b/Resources/public/templates/fields/edit/image.hbt index e1ad9143b..f1f9458a4 100644 --- a/Resources/public/templates/fields/edit/image.hbt +++ b/Resources/public/templates/fields/edit/image.hbt @@ -21,7 +21,7 @@

- Original image properties + Image properties (View the full width image)