diff --git a/app/assets/javascripts/controllers/admin/trainings.coffee.erb b/app/assets/javascripts/controllers/admin/trainings.coffee.erb index fcd583861..89535625e 100644 --- a/app/assets/javascripts/controllers/admin/trainings.coffee.erb +++ b/app/assets/javascripts/controllers/admin/trainings.coffee.erb @@ -1,35 +1,79 @@ 'use strict' +### COMMON CODE ### + +## +# Provides a set of common callback methods to the $scope parameter. These methods are used +# in the various trainings' admin controllers. +# +# Provides : +# - $scope.submited(content) +# - $scope.fileinputClass(v) +# +# Requires : +# - $state (Ui-Router) [ 'app.admin.trainings' ] +## +class TrainingsController + constructor: ($scope, $state) -> + + ## + # For use with ngUpload (https://github.com/twilson63/ngUpload). + # Intended to be the callback when the upload is done: any raised error will be stacked in the + # $scope.alerts array. If everything goes fine, the user is redirected to the trainings list. + # @param content {Object} JSON - The upload's result + ## + $scope.submited = (content) -> + if !content.id? + $scope.alerts = [] + angular.forEach content, (v, k)-> + angular.forEach v, (err)-> + $scope.alerts.push + msg: k+': '+err + type: 'danger' + else + $state.go('app.admin.trainings') + + + + ## + # Changes the current user's view, redirecting him to the machines list + ## + $scope.cancel = -> + $state.go('app.admin.trainings') + + + + ## + # For use with 'ng-class', returns the CSS class name for the uploads previews. + # The preview may show a placeholder or the content of the file depending on the upload state. + # @param v {*} any attribute, will be tested for truthiness (see JS evaluation rules) + ## + $scope.fileinputClass = (v)-> + if v + 'fileinput-exists' + else + 'fileinput-new' + ## # Controller used in the training creation page (admin) ## -Application.Controllers.controller "NewTrainingController", [ '$scope', 'machinesPromise', ($scope, machinesPromise) -> - - - - ### PUBLIC SCOPE ### +Application.Controllers.controller "NewTrainingController", [ '$scope', '$state', 'machinesPromise', 'CSRF' +, ($scope, $state, machinesPromise, CSRF) -> + CSRF.setMetaTags() ## Form action on the following URL $scope.method = 'post' ## API URL where the form will be posted - $scope.actionUrl = '/api/trainings' + $scope.actionUrl = '/api/trainings/' ## list of machines $scope.machines = machinesPromise - ## - # For use with 'ng-class', returns the CSS class name for the uploads previews. - # The preview may show a placeholder or the content of the file depending on the upload state. - # @param v {*} any attribute, will be tested for truthiness (see JS evaluation rules) - ## - $scope.fileinputClass = (v)-> - if v - 'fileinput-exists' - else - 'fileinput-new' + ## Using the TrainingsController + new TrainingsController($scope, $state) ] @@ -37,17 +81,15 @@ Application.Controllers.controller "NewTrainingController", [ '$scope', 'machine ## # Controller used in the training edition page (admin) ## -Application.Controllers.controller "EditTrainingController", [ '$scope', '$stateParams', 'trainingPromise', 'machinesPromise', ($scope, $stateParams, trainingPromise, machinesPromise) -> - - - - ### PUBLIC SCOPE ### +Application.Controllers.controller "EditTrainingController", [ '$scope', '$state', '$stateParams', 'trainingPromise', 'machinesPromise', 'CSRF' +, ($scope, $state, $stateParams, trainingPromise, machinesPromise, CSRF) -> + CSRF.setMetaTags() ## Form action on the following URL $scope.method = 'put' ## API URL where the form will be posted - $scope.actionUrl = '/api/trainings' + $stateParams.id + $scope.actionUrl = '/api/trainings/' + $stateParams.id ## Details of the training to edit (id in URL) $scope.training = trainingPromise @@ -55,16 +97,8 @@ Application.Controllers.controller "EditTrainingController", [ '$scope', '$state ## list of machines $scope.machines = machinesPromise - ## - # For use with 'ng-class', returns the CSS class name for the uploads previews. - # The preview may show a placeholder or the content of the file depending on the upload state. - # @param v {*} any attribute, will be tested for truthiness (see JS evaluation rules) - ## - $scope.fileinputClass = (v)-> - if v - 'fileinput-exists' - else - 'fileinput-new' + ## Using the TrainingsController + new TrainingsController($scope, $state) ] diff --git a/app/assets/templates/admin/trainings/_form.html.erb b/app/assets/templates/admin/trainings/_form.html.erb index 3c4b6044b..513efd04d 100644 --- a/app/assets/templates/admin/trainings/_form.html.erb +++ b/app/assets/templates/admin/trainings/_form.html.erb @@ -1,4 +1,11 @@ -
+ @@ -10,7 +17,12 @@
- + {{ 'name_is_required' }}
@@ -46,18 +58,22 @@
- + {{ 'description_is_required' }}
- +
- - + @@ -70,10 +86,11 @@
- +
@@ -83,7 +100,10 @@
diff --git a/app/assets/templates/machines/_form.html.erb b/app/assets/templates/machines/_form.html.erb index e38096571..f15478569 100644 --- a/app/assets/templates/machines/_form.html.erb +++ b/app/assets/templates/machines/_form.html.erb @@ -1,4 +1,11 @@ -
+ @@ -10,7 +17,13 @@
- + {{ 'name_is_required' }}
@@ -46,7 +59,14 @@
- + {{ 'description_is_required' }}
@@ -54,7 +74,14 @@
- + {{ 'technical_specifications_are_required' }}
@@ -83,7 +110,10 @@
diff --git a/app/controllers/api/trainings_controller.rb b/app/controllers/api/trainings_controller.rb index e17c1ed4a..d2ee21706 100644 --- a/app/controllers/api/trainings_controller.rb +++ b/app/controllers/api/trainings_controller.rb @@ -57,6 +57,6 @@ class API::TrainingsController < API::ApiController end def training_params - params.require(:training).permit(:id, :name, :description, :machine_ids, :plan_ids, :nb_total_places, machine_ids: [], plan_ids: []) + params.require(:training).permit(:id, :name, :description, :machine_ids, :plan_ids, :nb_total_places, training_image_attributes: [:attachment], machine_ids: [], plan_ids: []) end end diff --git a/app/models/training.rb b/app/models/training.rb index b2fa1463f..7670cb8bb 100644 --- a/app/models/training.rb +++ b/app/models/training.rb @@ -2,6 +2,9 @@ class Training < ActiveRecord::Base extend FriendlyId friendly_id :name, use: :slugged + has_one :training_image, as: :viewable, dependent: :destroy + accepts_nested_attributes_for :training_image, allow_destroy: true + has_and_belongs_to_many :machines, join_table: :trainings_machines has_many :trainings_availabilities @@ -23,7 +26,7 @@ class Training < ActiveRecord::Base after_update :update_statistic_subtype, if: :name_changed? after_destroy :remove_statistic_subtype - validates :description, length: { maximum: 255 } + validates_presence_of :description def amount_by_group(group) trainings_pricings.where(group_id: group).first diff --git a/app/models/training_image.rb b/app/models/training_image.rb new file mode 100644 index 000000000..084f84789 --- /dev/null +++ b/app/models/training_image.rb @@ -0,0 +1,4 @@ + +class TrainingImage < Asset + mount_uploader :attachment, MachineImageUploader +end \ No newline at end of file diff --git a/app/views/api/trainings/show.json.jbuilder b/app/views/api/trainings/show.json.jbuilder index 6afadde51..3621b8a78 100644 --- a/app/views/api/trainings/show.json.jbuilder +++ b/app/views/api/trainings/show.json.jbuilder @@ -1,4 +1,5 @@ -json.extract! @training, :id, :name, :machine_ids, :nb_total_places +json.extract! @training, :id, :name, :description, :machine_ids, :nb_total_places +json.training_image @training.training_image.attachment.large.url if @training.training_image json.availabilities @training.availabilities.order('start_at DESC') do |a| json.id a.id json.start_at a.start_at.iso8601 diff --git a/vendor/assets/components/angular-bootstrap/.bower.json b/vendor/assets/components/angular-bootstrap/.bower.json index 7f6da6277..e4c4cb6bb 100644 --- a/vendor/assets/components/angular-bootstrap/.bower.json +++ b/vendor/assets/components/angular-bootstrap/.bower.json @@ -25,7 +25,7 @@ "tag": "0.14.3", "commit": "306d1a30b4a8e8144741bb9c0126331ac884126a" }, - "_source": "git://github.com/angular-ui/bootstrap-bower.git", - "_target": ">=0.13.1", + "_source": "https://github.com/angular-ui/bootstrap-bower.git", + "_target": "~0.14.3", "_originalSource": "angular-bootstrap" } \ No newline at end of file diff --git a/vendor/assets/components/ngUpload/.bower.json b/vendor/assets/components/ngUpload/.bower.json index 85c076a0b..e0571b768 100644 --- a/vendor/assets/components/ngUpload/.bower.json +++ b/vendor/assets/components/ngUpload/.bower.json @@ -1,6 +1,6 @@ { "name": "ngUpload", - "version": "0.5.17", + "version": "0.5.18", "main": "ng-upload.js", "ignore": [ "node_modules", @@ -20,13 +20,13 @@ "angular": ">=1.0.4" }, "homepage": "https://github.com/twilson63/ngUpload", - "_release": "0.5.17", + "_release": "0.5.18", "_resolution": { "type": "version", - "tag": "v0.5.17", - "commit": "df9f3edfdbcd1ca6d3f365ff85e32de229df3af1" + "tag": "v0.5.18", + "commit": "da7fe2bb94eb6adb2cd26ab4f6f979aa020baf9c" }, - "_source": "git://github.com/twilson63/ngUpload.git", + "_source": "https://github.com/twilson63/ngUpload.git", "_target": ">=0.5.11", "_originalSource": "ngUpload" } \ No newline at end of file diff --git a/vendor/assets/components/ngUpload/bower.json b/vendor/assets/components/ngUpload/bower.json index 433d85f9c..77d66321d 100644 --- a/vendor/assets/components/ngUpload/bower.json +++ b/vendor/assets/components/ngUpload/bower.json @@ -1,6 +1,6 @@ { "name": "ngUpload", - "version": "0.5.17", + "version": "0.5.18", "main": "ng-upload.js", "ignore": [ "node_modules", diff --git a/vendor/assets/components/ngUpload/ng-upload.js b/vendor/assets/components/ngUpload/ng-upload.js index 931980e81..29d5e8d35 100644 --- a/vendor/assets/components/ngUpload/ng-upload.js +++ b/vendor/assets/components/ngUpload/ng-upload.js @@ -146,6 +146,9 @@ angular.module('ngUpload', []) } // perform check before submit file if (options.beforeSubmit && options.beforeSubmit(scope, {}) === false) { + if(!scope.$$phase){ + scope.$apply(); + } $event.preventDefault(); return false; } diff --git a/vendor/assets/components/ngUpload/ng-upload.min.js b/vendor/assets/components/ngUpload/ng-upload.min.js index 4131d1c81..92fc2a5c0 100644 --- a/vendor/assets/components/ngUpload/ng-upload.min.js +++ b/vendor/assets/components/ngUpload/ng-upload.min.js @@ -1 +1 @@ -angular.module("ngUpload",[]).directive("uploadSubmit",["$parse",function(){function n(t,e){t=angular.element(t);var a=t.parent();return e=e.toLowerCase(),a&&a[0].tagName.toLowerCase()===e?a:a?n(a,e):null}return{restrict:"AC",link:function(t,e){e.bind("click",function(t){if(t&&(t.preventDefault(),t.stopPropagation()),!e.attr("disabled")){var a=n(e,"form");a.triggerHandler("submit"),a[0].submit()}})}}}]).directive("ngUpload",["$log","$parse","$document",function(n,t,e){function a(n){var t,a=e.find("head");return angular.forEach(a.find("meta"),function(e){e.getAttribute("name")===n&&(t=e)}),angular.element(t)}var r=1;return{restrict:"AC",link:function(e,o,i){function l(n){e.$isUploading=n}function u(){s.unbind("load"),e.$$phase?l(!1):e.$apply(function(){l(!1)});try{var t,a=(s[0].contentDocument||s[0].contentWindow.document).body;try{t=angular.fromJson(a.innerText||a.textContent),e.$$phase?p(e,{content:t}):e.$apply(function(){p(e,{content:t})})}catch(r){t=a.innerHTML;var o="ng-upload: Response is not valid JSON";n.warn(o),f&&(e.$$phase?f(e,{error:o}):e.$apply(function(){f(e,{error:o})}))}}catch(o){n.warn("ng-upload: Server error"),f&&(e.$$phase?f(e,{error:o}):e.$apply(function(){f(e,{error:o})}))}}r++;var d={},p=i.ngUpload?t(i.ngUpload):null,f=i.errorCatcher?t(i.errorCatcher):null,c=i.ngUploadLoading?t(i.ngUploadLoading):null;i.hasOwnProperty("uploadOptionsConvertHidden")&&(d.convertHidden="false"!=i.uploadOptionsConvertHidden),i.hasOwnProperty("uploadOptionsEnableRailsCsrf")&&(d.enableRailsCsrf="false"!=i.uploadOptionsEnableRailsCsrf),i.hasOwnProperty("uploadOptionsBeforeSubmit")&&(d.beforeSubmit=t(i.uploadOptionsBeforeSubmit)),o.attr({target:"upload-iframe-"+r,method:"post",enctype:"multipart/form-data",encoding:"multipart/form-data"});var s=angular.element('