1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-01-17 06:52:27 +01:00

Merge branch 'training' into dev

This commit is contained in:
Sylvain 2016-07-13 18:15:14 +02:00
parent f5b435895e
commit 90142ae3bb
38 changed files with 737 additions and 172 deletions

View File

@ -1,7 +1,148 @@
'use strict'
Application.Controllers.controller "TrainingsController", ["$scope", "$state", "$uibModal", 'Training', 'trainingsPromise', 'machinesPromise', '_t', 'growl'
, ($scope, $state, $uibModal, Training, trainingsPromise, machinesPromise, _t, growl) ->
### 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', '$state', 'machinesPromise', 'CSRF'
, ($scope, $state, machinesPromise, CSRF) ->
### PUBLIC SCOPE ###
## Form action on the following URL
$scope.method = 'post'
## API URL where the form will be posted
$scope.actionUrl = '/api/trainings/'
## list of machines
$scope.machines = machinesPromise
### PRIVATE SCOPE ###
##
# Kind of constructor: these actions will be realized first when the controller is loaded
##
initialize = ->
CSRF.setMetaTags()
## Using the TrainingsController
new TrainingsController($scope, $state)
## !!! MUST BE CALLED AT THE END of the controller
initialize()
]
##
# Controller used in the training edition page (admin)
##
Application.Controllers.controller "EditTrainingController", [ '$scope', '$state', '$stateParams', 'trainingPromise', 'machinesPromise', 'CSRF'
, ($scope, $state, $stateParams, trainingPromise, machinesPromise, CSRF) ->
### PUBLIC SCOPE ###
## Form action on the following URL
$scope.method = 'patch'
## API URL where the form will be posted
$scope.actionUrl = '/api/trainings/' + $stateParams.id
## Details of the training to edit (id in URL)
$scope.training = trainingPromise
## list of machines
$scope.machines = machinesPromise
### PRIVATE SCOPE ###
##
# Kind of constructor: these actions will be realized first when the controller is loaded
##
initialize = ->
CSRF.setMetaTags()
## Using the TrainingsController
new TrainingsController($scope, $state)
## !!! MUST BE CALLED AT THE END of the controller
initialize()
]
##
# Controller used in the trainings management page, allowing admins users to see and manage the list of trainings and reservations.
##
Application.Controllers.controller "TrainingsAdminController", ["$scope", "$state", "$uibModal", 'Training', 'trainingsPromise', 'machinesPromise', '_t', 'growl', 'dialogs'
, ($scope, $state, $uibModal, Training, trainingsPromise, machinesPromise, _t, growl, dialogs) ->
@ -40,35 +181,6 @@ Application.Controllers.controller "TrainingsController", ["$scope", "$state", "
##
# Create a new empty training object and append it to the $scope.trainings list
##
$scope.addTraining = ->
$scope.inserted =
name: ''
machine_ids: []
$scope.trainings.push($scope.inserted)
##
# Saves a new training / Update an existing training to the server (form validation callback)
# @param data {Object} training name, associated machine(s) and default places number
# @param id {number} training id, in case of update
##
$scope.saveTraining = (data, id) ->
if id?
Training.update {id: id},
training: data
else
Training.save
training: data
, (resp) ->
$scope.trainings[$scope.trainings.length-1] = resp
console.log(resp)
##
# Removes the newly inserted but not saved training / Cancel the current training modification
# @param rowform {Object} see http://vitalets.github.io/angular-xeditable/
@ -138,30 +250,17 @@ Application.Controllers.controller "TrainingsController", ["$scope", "$state", "
# @param training {Object} training to delete
##
$scope.removeTraining = (index, training)->
training.$delete ->
$scope.trainings.splice(index, 1)
growl.info(_t('training_successfully_deleted'))
, (error)->
growl.warning(_t('unable_to_delete_the_training_because_some_users_alredy_booked_it'))
##
# Open the modal to edit description of the training
# @param training {Object} Training to edit description
##
$scope.openModalToSetDescription = (training)->
$uibModal.open(
templateUrl: "<%= asset_path 'admin/trainings/modal_edit.html' %>"
controller: ['$scope', '$uibModalInstance', 'Training', 'growl', ($scope, $uibModalInstance, Training, growl)->
$scope.training = training
$scope.save = ->
Training.update id: training.id, { training: { description: $scope.training.description } }, (training)->
$uibModalInstance.close()
growl.success(_t('description_was_successfully_saved'))
return
]
)
dialogs.confirm
resolve:
object: ->
title: _t('confirmation_required')
msg: _t('do_you_really_want_to_delete_this_training')
, -> # deletion confirmed
training.$delete ->
$scope.trainings.splice(index, 1)
growl.info(_t('training_successfully_deleted'))
, (error)->
growl.warning(_t('unable_to_delete_the_training_because_some_users_alredy_booked_it'))

View File

@ -19,7 +19,7 @@ Application.Controllers.controller "MainNavController", ["$scope", "$location",
linkIcon: 'calendar'
}
{
state: 'app.logged.trainings_reserve'
state: 'app.public.trainings_list'
linkText: 'trainings_registrations'
linkIcon: 'graduation-cap'
}

View File

@ -1,13 +1,58 @@
'use strict'
##
# Public listing of the trainings
##
Application.Controllers.controller "TrainingsController", ['$scope', '$state', 'trainingsPromise', ($scope, $state, trainingsPromise) ->
## List of trainings
$scope.trainings = trainingsPromise
##
# Callback for the 'reserve' button
##
$scope.reserveTraining = (training, event) ->
$state.go('app.logged.trainings_reserve', {id: training.id})
##
# Callback for the 'show' button
##
$scope.showTraining = (training) ->
$state.go('app.public.training_show', {id: training.id})
]
##
# Public view of a specific training
##
Application.Controllers.controller "ShowTrainingController", ['$scope', '$state', 'trainingPromise', ($scope, $state, trainingPromise) ->
## Current training
$scope.training = trainingPromise
##
# Callback for the 'reserve' button
##
$scope.reserveTraining = (training, event) ->
$state.go('app.logged.trainings_reserve', {id: training.id})
##
# Revert view to the full list of trainings ("<-" button)
##
$scope.cancel = (event) ->
$state.go('app.public.trainings_list')
]
##
# Controller used in the training reservation agenda page.
# This controller is very similar to the machine reservation controller with one major difference: here, ONLY ONE
# training can be reserved during the reservation process (the shopping cart may contains only one training and a subscription).
##
Application.Controllers.controller "ReserveTrainingController", ["$scope", "$state", '$stateParams', "$uibModal", 'Auth', 'dialogs', '$timeout', 'Price', 'Availability', 'Slot', 'Member', 'Setting', 'CustomAsset', '$compile', 'availabilityTrainingsPromise', 'plansPromise', 'groupsPromise', 'growl', 'settingsPromise', '_t',
($scope, $state, $stateParams, $uibModal, Auth, dialogs, $timeout, Price, Availability, Slot, Member, Setting, CustomAsset, $compile, availabilityTrainingsPromise, plansPromise, groupsPromise, growl, settingsPromise, _t) ->
Application.Controllers.controller "ReserveTrainingController", ["$scope", "$state", '$stateParams', '$filter', '$compile', "$uibModal", 'Auth', 'dialogs', '$timeout', 'Price', 'Availability', 'Slot', 'Member', 'Setting', 'CustomAsset', 'availabilityTrainingsPromise', 'plansPromise', 'groupsPromise', 'growl', 'settingsPromise', 'trainingPromise', '_t'
, ($scope, $state, $stateParams, $filter, $compile, $uibModal, Auth, dialogs, $timeout, Price, Availability, Slot, Member, Setting, CustomAsset, availabilityTrainingsPromise, plansPromise, groupsPromise, growl, settingsPromise, trainingPromise, _t) ->
@ -71,6 +116,9 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
## Once a training reservation was modified, will contains {newReservedSlot:{}, oldReservedSlot:{}}
$scope.modifiedSlots = null
## Selected training unless 'all' trainings are displayed
$scope.training = trainingPromise
## fullCalendar (v2) configuration
$scope.calendarConfig =
timezone: Fablab.timezone
@ -125,7 +173,7 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
if $scope.ctrl.member
Member.get {id: $scope.ctrl.member.id}, (member) ->
$scope.ctrl.member = member
Availability.trainings {member_id: $scope.ctrl.member.id}, (trainings) ->
Availability.trainings {trainingId: $stateParams.id, member_id: $scope.ctrl.member.id}, (trainings) ->
$scope.calendar.fullCalendar 'removeEvents'
$scope.eventSources.push
events: trainings
@ -441,7 +489,7 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
##
eventRenderCb = (event, element, view)->
element.attr(
'uib-popover': event.training.description
'uib-popover': $filter('humanize')($filter('simpleText')(event.training.description), 70)
'popover-trigger': 'mouseenter'
)
$compile(element)($scope)

View File

@ -115,6 +115,8 @@ Application.Filters.filter "simpleText", [ ->
if text != undefined
text = text.replace(/<br\s*\/?>/g, '\n')
text.replace(/<\/?\w+[^>]*>/g, '')
else
""
]
Application.Filters.filter "toTrusted", [ "$sce", ($sce) ->

View File

@ -363,8 +363,34 @@ angular.module('application.router', ['ui.router']).
Translations.query(['app.admin.machines_edit', 'app.shared.machine']).$promise
]
# trainings
.state 'app.public.trainings_list',
url: '/trainings'
views:
'main@':
templateUrl: '<%= asset_path "trainings/index.html" %>'
controller: 'TrainingsController'
resolve:
trainingsPromise: ['Training', (Training)->
Training.query().$promise
]
translations: [ 'Translations', (Translations) ->
Translations.query(['app.public.trainings_list']).$promise
]
.state 'app.public.training_show',
url: '/trainings/:id'
views:
'main@':
templateUrl: '<%= asset_path "trainings/show.html" %>'
controller: 'ShowTrainingController'
resolve:
trainingPromise: ['Training', '$stateParams', (Training, $stateParams)->
Training.get({id: $stateParams.id}).$promise
]
translations: [ 'Translations', (Translations) ->
Translations.query(['app.public.training_show']).$promise
]
.state 'app.logged.trainings_reserve',
url: '/trainings/reserve'
url: '/trainings/:id/reserve'
views:
'main@':
templateUrl: '<%= asset_path "trainings/reserve.html" %>'
@ -379,8 +405,11 @@ angular.module('application.router', ['ui.router']).
groupsPromise: ['Group', (Group)->
Group.query().$promise
]
availabilityTrainingsPromise: ['Availability', (Availability)->
Availability.trainings().$promise
availabilityTrainingsPromise: ['Availability', '$stateParams', (Availability, $stateParams)->
Availability.trainings({trainingId: $stateParams.id}).$promise
]
trainingPromise: ['Training', '$stateParams', (Training, $stateParams)->
Training.get({id: $stateParams.id}).$promise unless $stateParams.id == 'all'
]
settingsPromise: ['Setting', (Setting)->
Setting.query(names: "['booking_window_start',
@ -511,7 +540,7 @@ angular.module('application.router', ['ui.router']).
views:
'main@':
templateUrl: '<%= asset_path "admin/trainings/index.html" %>'
controller: 'TrainingsController'
controller: 'TrainingsAdminController'
resolve:
trainingsPromise: ['Training', (Training)->
Training.query().$promise
@ -520,9 +549,37 @@ angular.module('application.router', ['ui.router']).
Machine.query().$promise
]
translations: [ 'Translations', (Translations) ->
Translations.query('app.admin.trainings').$promise
Translations.query(['app.admin.trainings', 'app.shared.trainings']).$promise
]
.state 'app.admin.trainings_new',
url: '/admin/trainings/new'
views:
'main@':
templateUrl: '<%= asset_path "admin/trainings/new.html" %>'
controller: 'NewTrainingController'
resolve:
machinesPromise: ['Machine', (Machine)->
Machine.query().$promise
]
translations: [ 'Translations', (Translations) ->
Translations.query(['app.admin.trainings_new', 'app.shared.trainings']).$promise
]
.state 'app.admin.trainings_edit',
url: '/admin/trainings/:id/edit'
views:
'main@':
templateUrl: '<%= asset_path "admin/trainings/edit.html" %>'
controller: 'EditTrainingController'
resolve:
trainingPromise: ['Training', '$stateParams', (Training, $stateParams)->
Training.get(id: $stateParams.id).$promise
]
machinesPromise: ['Machine', (Machine)->
Machine.query().$promise
]
translations: [ 'Translations', (Translations) ->
Translations.query('app.shared.trainings').$promise
]
# events
.state 'app.admin.events',
url: '/admin/events'

View File

@ -14,7 +14,8 @@ Application.Services.factory 'Availability', ["$resource", ($resource)->
isArray: true
trainings:
method: 'GET'
url: '/api/availabilities/trainings'
url: '/api/availabilities/trainings/:trainingId'
params: {trainingId: "@trainingId"}
isArray: true
update:
method: 'PUT'

View File

@ -23,7 +23,7 @@
<ng-include src="'<%= asset_path 'admin/plans/_form.html' %>'"></ng-include>
<div class="panel-footer no-padder">
<input type="submit" value="Enregistrer" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="planForm.$invalid || !partnerIsValid()"/>
<input type="submit" value="{{ 'save' | translate }}" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="planForm.$invalid || !partnerIsValid()"/>
</div>
</form>

View File

@ -0,0 +1,104 @@
<form role="form"
name="trainingForm"
class="form-horizontal"
ng-attr-action="{{ actionUrl }}"
ng-upload="submited(content)"
upload-options-enable-rails-csrf="true"
unsaved-warning-form
novalidate>
<input name="_method" type="hidden" ng-value="method">
<section class="panel panel-default bg-light m-lg">
<div class="panel-body m-r">
<uib-alert ng-repeat="alert in alerts" type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</uib-alert>
<div class="form-group m-b-lg" ng-class="{'has-error': trainingForm['training[name]'].$dirty && trainingForm['training[name]'].$invalid}">
<label for="name" class="col-sm-2 control-label">{{ 'name' | translate }} *</label>
<div class="col-sm-4">
<input name="training[name]"
ng-model="training.name"
type="text"
class="form-control"
id="training_name"
placeholder="{{'name' | translate}}"
required/>
<span class="help-block" ng-show="trainingForm['training[name]'].$dirty && trainingForm['training[name]'].$error.required" translate>{{ 'name_is_required' }}</span>
</div>
</div>
<div class="form-group m-b-lg">
<label for="training_image" class="col-sm-2 control-label">{{ 'illustration' | translate }} *</label>
<div class="col-sm-10">
<div class="fileinput" data-provides="fileinput" ng-class="fileinputClass(training.training_image)">
<div class="fileinput-new thumbnail" style="width: 334px; height: 250px;">
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:&#xf03e;/font:FontAwesome/icon" bs-holder ng-if="!training.training_image">
</div>
<div class="fileinput-preview fileinput-exists thumbnail" style="max-width: 334px;">
<img ng-src="{{ training.training_image }}" alt="" />
</div>
<div>
<span class="btn btn-default btn-file">
<span class="fileinput-new">{{ 'add_an_illustration' | translate }} <i class="fa fa-upload fa-fw"></i></span>
<span class="fileinput-exists" translate>{{ 'change' }}</span>
<input type="file"
ng-model="training.training_image"
name="training[training_image_attributes][attachment]"
accept="image/*"
required
bs-jasny-fileinput>
</span>
<a href="#" class="btn btn-danger fileinput-exists" data-dismiss="fileinput" translate>{{ 'delete' }}</a>
</div>
</div>
</div>
</div>
<div class="form-group m-b-xl" ng-class="{'has-error': trainingForm['training[description]'].$dirty && trainingForm['training[description]'].$invalid}">
<label for="training_description" class="col-sm-2 control-label">{{ 'description' | translate }} *</label>
<div class="col-sm-10">
<input type="hidden" name="training[description]" ng-value="training.description" />
<summernote ng-model="training.description" id="training_description" placeholder="" config="summernoteOpts" name="training[description]" required></summernote>
<span class="help-block" ng-show="trainingForm['training[description]'].$dirty && trainingForm['training[description]'].$error.required" translate>{{ 'description_is_required' }}</span>
</div>
</div>
<div class="form-group m-b-lg" ng-class="{'has-error': trainingForm['training[machine_ids]'].$dirty && trainingForm['training[machine_ids]'].$invalid}">
<label for="training_machines" class="col-sm-2 control-label">{{ 'associated_machines' | translate }}</label>
<div class="col-sm-4">
<ui-select multiple ng-model="training.machine_ids" class="form-control" id="training_machines">
<ui-select-match>
<span ng-bind="$item.name"></span>
<input type="hidden" name="training[machine_ids][]" value="{{$item.id}}" />
</ui-select-match>
<ui-select-choices repeat="m.id as m in (machines | filter: $select.search)">
<span ng-bind-html="m.name | highlight: $select.search"></span>
</ui-select-choices>
</ui-select>
</div>
</div>
<div class="form-group m-b-lg" ng-class="{'has-error': trainingForm['training[nb_total_places]'].$dirty && trainingForm['training[nb_total_places]'].$invalid}">
<label for="training_nb_total_places" class="col-sm-2 control-label">{{ 'number_of_tickets' | translate }}</label>
<div class="col-sm-4">
<input ng-model="training.nb_total_places"
type="number"
min="0"
name="training[nb_total_places]"
class="form-control"
id="training_nb_total_places">
</div>
</div>
</div> <!-- ./panel-body -->
<div class="panel-footer no-padder">
<input type="submit"
value="{{ 'validate_your_training' | translate }}"
class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c"
ng-disabled="trainingForm.$invalid"/>
</div>
</section>
</form>

View File

@ -0,0 +1,27 @@
<section class="heading b-b">
<div class="row no-gutter">
<div class="col-xs-2 col-sm-2 col-md-1">
<section class="heading-btn">
<a ng-click="cancel()"><i class="fa fa-long-arrow-left"></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
<section class="heading-title">
<h1>{{ training.name }}</h1>
</section>
</div>
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md">
<section class="heading-actions wrapper">
<div class="btn btn-lg btn-block btn-default rounded m-t-xs" ng-click="cancel()" translate>{{ 'cancel' }}</div>
</section>
</div>
</div>
</section>
<div class="row no-gutter">
<div class="col-sm-12 col-md-12 col-lg-9 b-r-lg nopadding">
<ng-include src="'<%= asset_path 'admin/trainings/_form.html' %>'"></ng-include>
</div>
</div>

View File

@ -21,11 +21,7 @@
<div class="col-md-12">
<uib-tabset justified="true">
<uib-tab heading="{{ 'trainings' | translate }}">
<button type="button" class="btn btn-warning m-t m-b" ng-click="addTraining()" translate>{{ 'add_a_new_training' }}</button>
<div class="alert alert-warning" role="alert">
{{ 'beware_when_creating_a_training_its_reservation_prices_are_initialized_to_zero' | translate }}
{{ 'dont_forget_to_change_them_before_creating_slots_for_this_training' | translate }}
</div>
<button type="button" class="btn btn-warning m-t m-b" ui-sref="app.admin.trainings_new" translate>{{ 'add_a_new_training' }}</button>
<table class="table">
<thead>
@ -38,35 +34,12 @@
</thead>
<tbody>
<tr ng-repeat="training in trainings">
<td>{{ training.name }}</td>
<td>{{ showMachines(training) }}</td>
<td>{{ training.nb_total_places }}</td>
<td>
<span editable-text="training.name" e-name="name" e-form="rowform" e-required>
{{ training.name }}
</span>
</td>
<td>
<span editable-checklist="training.machine_ids" e-ng-options="m.id as m.name for m in machines" e-name="machine_ids" e-form="rowform">
{{ showMachines(training) }}
</span>
</td>
<td>
<span editable-number="training.nb_total_places" e-name="nb_total_places" e-form="rowform" e-required>
{{ training.nb_total_places }}
</span>
</td>
<td>
<form editable-form name="rowform" onbeforesave="saveTraining($data, training.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted == training">
<button type="submit" ng-disabled="rowform.$waiting" class="btn btn-warning">
<i class="fa fa-check"></i>
</button>
<button type="button" ng-disabled="rowform.$waiting" ng-click="cancelTraining(rowform, $index)" class="btn btn-default">
<i class="fa fa-times"></i>
</button>
</form>
<div class="buttons" ng-show="!rowform.$visible">
<button ng-click="openModalToSetDescription(training)" class="btn btn-default">
<i class="fa fa-comment-o"></i>
</button>
<button class="btn btn-default" ng-click="rowform.$show()">
<div class="buttons">
<button class="btn btn-default" ui-sref="app.admin.trainings_edit({id:training.id})">
<i class="fa fa-edit"></i> {{ 'edit' | translate }}
</button>
<button class="btn btn-danger" ng-click="removeTraining($index, training)">

View File

@ -0,0 +1,35 @@
<section class="heading b-b">
<div class="row no-gutter">
<div class="col-md-1 hidden-xs">
<section class="heading-btn">
<a href="#" ng-click="cancel()"><i class="fa fa-long-arrow-left "></i></a>
</section>
</div>
<div class="col-md-8 b-l b-r">
<section class="heading-title">
<h1 translate>{{ 'add_a_new_training' }}</h1>
</section>
</div>
</div>
</section>
<div class="row no-gutter" >
<div class="col-md-9 b-r nopadding">
<div class="alert alert-warning m-lg" role="alert">
{{ 'beware_when_creating_a_training_its_reservation_prices_are_initialized_to_zero' | translate }}
{{ 'dont_forget_to_change_them_before_creating_slots_for_this_training' | translate }}
</div>
<ng-include src="'<%= asset_path 'admin/trainings/_form.html' %>'"></ng-include>
</div>
<div class="col-md-3">
<!-- <button class="btn">TEST</button> -->
</div>
</div>

View File

@ -1,4 +1,11 @@
<form role="form" name="machineForm" class="form-horizontal" novalidate action="{{ actionUrl }}" ng-upload="submited(content)" upload-options-enable-rails-csrf="true" unsaved-warning-form>
<form role="form"
name="machineForm"
class="form-horizontal"
action="{{ actionUrl }}"
ng-upload="submited(content)"
upload-options-enable-rails-csrf="true"
unsaved-warning-form
novalidate>
<input name="_method" type="hidden" ng-value="method">
@ -10,7 +17,13 @@
<div class="form-group m-b-lg" ng-class="{'has-error': machineForm['machine[name]'].$dirty && machineForm['machine[name]'].$invalid}">
<label for="name" class="col-sm-2 control-label">{{ 'name' | translate }} *</label>
<div class="col-sm-4">
<input ng-model="machine.name" type="text" name="machine[name]" class="form-control" id="machine_name" placeholder="Nom :" required>
<input ng-model="machine.name"
type="text"
name="machine[name]"
class="form-control"
id="machine_name"
placeholder="{{'name' | translate}}"
required>
<span class="help-block" ng-show="machineForm['machine[name]'].$dirty && machineForm['machine[name]'].$error.required" translate>{{ 'name_is_required' }}</span>
</div>
</div>
@ -46,8 +59,16 @@
<div class="form-group m-b-xl" ng-class="{'has-error': machineForm['machine[description]'].$dirty && machineForm['machine[description]'].$invalid}">
<label for="description" class="col-sm-2 control-label">{{ 'description' | translate }} *</label>
<div class="col-sm-10">
<input type="hidden" name="machine[description]" ng-value="machine.description" />
<summernote ng-model="machine.description" id="machine_description" placeholder="" config="summernoteOpts" name="machine[description]" required></summernote>
<input type="hidden"
name="machine[description]"
ng-value="machine.description" />
<summernote ng-model="machine.description"
id="machine_description"
placeholder=""
config="summernoteOpts"
name="machine[description]"
required>
</summernote>
<span class="help-block" ng-show="machineForm['machine[description]'].$dirty && machineForm['machine[description]'].$error.required" translate>{{ 'description_is_required' }}</span>
</div>
</div>
@ -55,8 +76,16 @@
<div class="form-group m-b-xl" ng-class="{'has-error': machineForm['machine[spec]'].$dirty && machineForm['machine[spec]'].$invalid}">
<label for="spec" class="col-sm-2 control-label">{{ 'technical_specifications' | translate }} *</label>
<div class="col-sm-10">
<input type="hidden" name="machine[spec]" ng-value="machine.spec" />
<summernote ng-model="machine.spec" id="machine_spec" placeholder="" config="summernoteOpts" name="machine[spec]" required></summernote>
<input type="hidden"
name="machine[spec]"
ng-value="machine.spec" />
<summernote ng-model="machine.spec"
id="machine_spec"
placeholder=""
config="summernoteOpts"
name="machine[spec]"
required>
</summernote>
<span class="help-block" ng-show="machineForm['machine[spec]'].$dirty && machineForm['machine[spec]'].$error.required" translate>{{ 'technical_specifications_are_required' }}</span>
</div>
</div>
@ -85,7 +114,10 @@
</div> <!-- ./panel-body -->
<div class="panel-footer no-padder">
<input type="submit" value="{{ 'validate_your_machine' | translate }}" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="machineForm.$invalid"/>
<input type="submit"
value="{{ 'validate_your_machine' | translate }}"
class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c"
ng-disabled="machineForm.$invalid"/>
</div>
</section>
</form>

View File

@ -0,0 +1,61 @@
<section class="heading b-b">
<div class="row no-gutter">
<div class="col-xs-2 col-sm-2 col-md-1">
<section class="heading-btn">
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
<section class="heading-title">
<h1 translate>{{ 'the_trainings' }}</h1>
</section>
</div>
</div>
</section>
<section class="m-lg">
<div class="row" ng-repeat="training in (trainings.length/3 | array)">
<div class="col-xs-12 col-sm-6 col-md-4" ng-repeat="training in trainings.slice(3*$index, 3*$index + 3)">
<div class="widget panel panel-default">
<div class="panel-heading picture" ng-if="!training.training_image">
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:&#xf03e;/font:FontAwesome/icon" bs-holder class="img-responsive">
</div>
<div class="panel-heading picture" style="background-image:url({{training.training_image}})" ng-if="training.training_image">
</div>
<div class="panel-body" style="heigth:170px;">
<h1 class="m-b">{{training.name}}</h1>
<div ng-if="training.description">
<p ng-bind-html="training.description | simpleText | humanize : 140 | breakFilter"></p>
</div>
</div>
<div class="panel-footer no-padder">
<div class="text-center clearfix">
<div class="col-sm-6 b-r no-padder">
<div class="btn btn-default btn-block no-b padder-v red" ng-click="reserveTraining(training, $event)">
<i class="fa fa-bookmark"></i> {{ 'book' | translate }}
</div>
</div>
<div class="col-sm-6 no-padder">
<div class="btn btn-default btn-block padder-v no-b red" ng-click="showTraining(training)">
<i class="fa fa-eye"></i> {{ 'consult' | translate }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>

View File

@ -5,11 +5,20 @@
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
<section class="heading-title">
<h1 translate>{{ 'trainings_planning' }}</h1>
<h1 ng-hide="training" translate>{{ 'trainings_planning' }}</h1>
<h1 ng-show="training"><span translate>{{ 'planning_of' }}</span> {{training.name}}</h1>
</section>
</div>
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md">
<section class="heading-actions wrapper">
<a class="btn btn-lg btn-warning bg-white b-2x rounded m-t-xs"
ui-sref="app.logged.trainings_reserve({id:'all'})"
ng-show="training"
role="button"
translate>{{ 'all_trainings' }}</a>
</section>
</div>
</div>
</section>
@ -24,6 +33,10 @@
<div class="col-sm-12 col-md-12 col-lg-3">
<div class="text-center m-t">
</div>
<div ng-if="currentUser.role === 'admin'">
<select-member></select-member>
</div>

View File

@ -0,0 +1,42 @@
<div>
<section class="heading b-b">
<div class="row no-gutter">
<div class="col-xs-2 col-sm-2 col-md-1">
<section class="heading-btn">
<a href="#" ng-click="cancel($event)"><i class="fa fa-long-arrow-left "></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-7 b-l b-r-md">
<section class="heading-title">
<h1>{{ training.name }}</h1>
</section>
</div>
<div class="col-xs-12 col-sm-12 col-md-4 b-t hide-b-md">
<section class="heading-actions wrapper">
<a ng-click="reserveTraining(training, $event)" class="btn btn-lg btn-warning bg-white b-2x rounded m-t-xs" translate>{{ 'book_this_training' }}</a>
</section>
</div>
</div>
</section>
<div class="row no-gutter">
<div class="col-sm-12 col-md-12 col-lg-8 b-r-lg">
<div class="article wrapper-lg" >
<div class="article-thumbnail" ng-if="training.training_image">
<img ng-src="{{training.training_image}}" alt="{{training.name}}" class="img-responsive">
</div>
<p class="intro" ng-bind-html="training.description | breakFilter"></p>
</div>
</div>
</div>
</div>

View File

@ -78,14 +78,33 @@ class API::AvailabilitiesController < API::ApiController
@user = current_user
end
@slots = []
@reservations = @user.reservations.includes(:slots).references(:slots).where("reservable_type = 'Training' AND slots.start_at > ?", Time.now)
# first, we get the already-made reservations
@reservations = @user.reservations.where("reservable_type = 'Training'")
@reservations = @reservations.where('reservable_id = :id', id: params[:training_id].to_i) if params[:training_id].is_number?
@reservations = @reservations.joins(:slots).where('slots.start_at > ?', Time.now)
# what is requested?
# 1) a single training
if params[:training_id].is_number?
@availabilities = Training.find(params[:training_id]).availabilities
# 2) all trainings
else
@availabilities = Availability.trainings
end
# who made the request?
# 1) an admin (he can see all future availabilities)
if @user.is_admin?
@availabilities = Availability.includes(:tags, :slots, trainings: [:machines]).trainings.where('availabilities.start_at > ?', Time.now)
@availabilities = @availabilities.includes(:tags, :slots, trainings: [:machines]).where('availabilities.start_at > ?', Time.now)
# 2) an user (he cannot see availabilities further than 1 (or 3) months)
else
end_at = 1.month.since
end_at = 3.months.since if can_show_slot_plus_three_months(@user)
@availabilities = Availability.includes(:tags, :slots, trainings: [:machines]).trainings.where('availabilities.start_at > ? AND availabilities.start_at < ?', Time.now, end_at).where('availability_tags.tag_id' => @user.tag_ids.concat([nil]))
@availabilities = @availabilities.includes(:tags, :slots, :availability_tags, trainings: [:machines]).where('availabilities.start_at > ? AND availabilities.start_at < ?', Time.now, end_at).where('availability_tags.tag_id' => @user.tag_ids.concat([nil]))
end
# finally, we merge the availabilities with the reservations
@availabilities.each do |a|
a = verify_training_is_reserved(a, @reservations)
end

View File

@ -35,10 +35,15 @@ class API::TrainingsController < API::ApiController
members.each do |m|
m.trainings << @training
end
head :no_content
else
@training.update(training_params)
if @training.update(training_params)
render :show, status: :ok, location: @training
else
render json: @training.errors, status: :unprocessable_entity
end
end
head :no_content
end
def destroy
@ -57,6 +62,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

View File

@ -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,8 +26,6 @@ class Training < ActiveRecord::Base
after_update :update_statistic_subtype, if: :name_changed?
after_destroy :remove_statistic_subtype
validates :description, length: { maximum: 255 }
def amount_by_group(group)
trainings_pricings.where(group_id: group).first
end

View File

@ -0,0 +1,4 @@
class TrainingImage < Asset
mount_uploader :attachment, MachineImageUploader
end

View File

@ -2,12 +2,8 @@ role = (current_user and current_user.is_admin?) ? 'admin' : 'user'
json.cache! [@trainings, role] do
json.array!(@trainings) do |training|
json.id training.id
json.name training.name
json.description training.description
json.machine_ids training.machine_ids
json.nb_total_places training.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.plan_ids training.plan_ids if role === 'admin'
end
end

View File

@ -1,5 +1,6 @@
json.extract! @training, :id, :name, :machine_ids, :nb_total_places
json.availabilities @training.availabilities do |a|
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
json.end_at a.end_at.iso8601

View File

@ -0,0 +1,12 @@
## Helper method: will return true if the current string
## can be parsed as a number (float or integer), false otherwise
# exemples:
# "2" => true
# "4.5" => true
# "hello" => false
# "" => false
class String
def is_number?
true if Float(self) rescue false
end
end

View File

@ -46,11 +46,6 @@ en:
trainings:
# track and monitor the trainings
add_a_new_training: "Add a new training"
beware_when_creating_a_training_its_reservation_prices_are_initialized_to_zero: "Beware, when creating a training, its reservation prices are initialized at zero."
dont_forget_to_change_them_before_creating_slots_for_this_training: "Don't forget to change them before creating slots for this training."
associated_machines: "Associated machines"
number_of_tickets: "Number of tickets"
training: "Training"
year_NUMBER: "Year {{NUMBER}}" # angular interpolation
month_of_NAME: "Month of {{NAME}}" # angular interpolation
@ -67,6 +62,12 @@ en:
description_was_successfully_saved: "Description was successfully saved."
training_successfully_deleted: "Training successfully deleted."
unable_to_delete_the_training_because_some_users_alredy_booked_it: "Unable to delete the training because some users already booked it."
do_you_really_want_to_delete_this_training: "Do you really want to delete this training?"
trainings_new:
# create a new training
beware_when_creating_a_training_its_reservation_prices_are_initialized_to_zero: "Beware, when creating a training, its reservation prices are initialized at zero."
dont_forget_to_change_them_before_creating_slots_for_this_training: "Don't forget to change them before creating slots for this training."
events:
# courses and workshops tracking and management

View File

@ -46,11 +46,6 @@ fr:
trainings:
# suivre et surveiller les formations
add_a_new_training: "Ajouter une nouvelle formation"
beware_when_creating_a_training_its_reservation_prices_are_initialized_to_zero: "Attention, lors de la création d'une formation, ses tarifs de réservation sont initialisés à zero."
dont_forget_to_change_them_before_creating_slots_for_this_training: "Pensez à les modifier avant de créer des créneaux pour cette formation."
associated_machines: "Machines associées"
number_of_tickets: "Nombre de places"
training: "Formation"
year_NUMBER: "Année {{NUMBER}}" # angular interpolation
month_of_NAME: "Mois de {{NAME}}" # angular interpolation
@ -67,6 +62,12 @@ fr:
description_was_successfully_saved: "La description a bien été enregistrée."
training_successfully_deleted: "La formation a bien été supprimée."
unable_to_delete_the_training_because_some_users_alredy_booked_it: "La formation ne peut pas être supprimée car elle a déjà été réservée par des utilisateurs."
do_you_really_want_to_delete_this_training: "Êtes-vous sur de vouloir supprimer cette formation ?"
trainings_new:
# créer une nouvelle formation
beware_when_creating_a_training_its_reservation_prices_are_initialized_to_zero: "Attention, lors de la création d'une formation, ses tarifs de réservation sont initialisés à zero."
dont_forget_to_change_them_before_creating_slots_for_this_training: "Pensez à les modifier avant de créer des créneaux pour cette formation."
events:
# gestion et suivi des stages et ateliers

View File

@ -128,6 +128,8 @@ en:
trainings_reserve:
# book a training
trainings_planning: "Trainings planning"
planning_of: "Planning of" # followed by the training name (eg. "Planning of 3d printer training")
all_trainings: "All trainings"
select_a_slot_in_the_calendar: "Select a slot in the calendar"
you_ve_just_selected_the_slot: "You've just selected the slot:"
datetime_to_time: "{{START_DATETIME}} to {{END_TIME}}" # angular interpolation, eg: Thursday, September 4 1986 8:30 PM to 10:00 PM

View File

@ -127,7 +127,9 @@ fr:
trainings_reserve:
# réserver une formation
trainings_planning: "Planning formation"
trainings_planning: "Planning formations"
planning_of: "Planning de la" # suivi du nom de la formation (eg. "Planning de la formation imprimante 3d")
all_trainings: "Toutes les formations"
select_a_slot_in_the_calendar: "Sélectionnez un créneau dans le calendrier"
you_ve_just_selected_the_slot: "Vous venez de sélectionner le créneau :"
datetime_to_time: "{{START_DATETIME}} à {{END_TIME}}" # angular interpolation, eg: Thursday, September 4 1986 8:30 PM to 10:00 PM

View File

@ -161,7 +161,6 @@ en:
# list of machines
the_fablab_s_machines: "The FabLab's machines"
add_a_machine: "Add a machine"
book: "Book"
_or_the_: " or the "
machines_show:
@ -173,6 +172,14 @@ en:
unauthorized_operation: "Unauthoried operation"
the_machine_cant_be_deleted_because_it_is_already_reserved_by_some_users: "The machine can't be deleted because it's already reserved by some users."
trainings_list:
# list of trainings
the_trainings: "The trainings"
training_show:
# details of a training
book_this_training: "Book this training"
plans:
# summary of the subscriptions
subcriptions: "Subscriptions"

View File

@ -161,7 +161,6 @@ fr:
# liste des machines
the_fablab_s_machines: "Les machines du FabLab"
add_a_machine: "Ajouter une machine"
book: "Réserver"
_or_the_: " ou la "
machines_show:
@ -173,6 +172,16 @@ fr:
unauthorized_operation: "Opération non autorisée"
the_machine_cant_be_deleted_because_it_is_already_reserved_by_some_users: "La machine ne peut pas être supprimée car elle a déjà été réservée par des utilisateurs."
trainings_list:
# liste des formations
the_trainings: "Les formations"
training_show:
# détails d'une formation
book_this_training: "Réserver cette formation"
plans:
# page récapitulative des abonnements
subcriptions: "Les abonnements"

View File

@ -87,6 +87,11 @@ en:
_disconnect_then_reconnect_: "disconnect then reconnect"
_for_your_changes_to_take_effect: "for your changes to take effect."
add_a_project: "Add a project"
illustration: "Illustration"
add_an_illustration: "Add an illustration."
book: "Book"
description_is_required: "Description is required."
name_is_required: "Name is required."
messages:
you_will_lose_any_unsaved_modification_if_you_quit_this_page: "You will lose any unsaved modification if you quit this page"
@ -110,12 +115,10 @@ en:
project:
# project edition form
name_is_required: "Name is required."
illustration: "Illustration"
add_an_illustration: "Add an illustration"
CAD_file: "CAD file"
add_a_new_file: "Add a new file"
description_is_required: "Description is required."
steps: "Steps"
step_title: "Step title"
add_a_picture: "Add a picture"
@ -130,10 +133,6 @@ en:
machine:
# machine edition form
name_is_required: "The name is required."
illustration: "Illustration"
add_an_illustration: "Add an illustration."
description_is_required: "Description is required."
technical_specifications_are_required: "Technical specifications are required."
attached_files_(pdf): "Attached files (pdf)"
attach_a_file: "Attach a file"
@ -168,7 +167,6 @@ en:
title_is_required: "Title is required."
matching_visual: "Matching visual"
choose_a_picture: "Choose a picture"
description_is_required: "Description is required."
attachments: "Attachments"
add_a_new_file: "Add a new file"
event_type: "Event type"
@ -188,7 +186,6 @@ en:
plan:
# subscription plan edition form
general_informations: "General informations"
name_is_required: "Name is required."
name_length_must_be_less_than_24_characters: "Name length must be less than 24 characters."
type_is_required: "Type is required."
group: "Group"
@ -213,6 +210,13 @@ en:
new_partner: "New partner"
email_address_is_required: "Email address is required."
trainings:
# training edition form
add_a_new_training: "Add a new training"
validate_your_training: "Validate your training"
associated_machines: "Associated machines"
number_of_tickets: "Number of tickets"
user_admin:
# partial form to edit/create an user (admin view)
group: "Group"

View File

@ -87,6 +87,11 @@ fr:
_disconnect_then_reconnect_: "déconnectez-vous puis re-connectez vous"
_for_your_changes_to_take_effect: "pour que les modifications soient prises en compte."
add_a_project: "Ajouter un projet"
illustration: "Visuel"
add_an_illustration: "Ajouter un visuel"
book: "Réserver"
description_is_required: "La description est requise."
name_is_required: "Le nom est requis."
messages:
you_will_lose_any_unsaved_modification_if_you_quit_this_page: "Vous perdrez les modifications non enregistrées si vous quittez cette page"
@ -110,12 +115,10 @@ fr:
project:
# formulaire d'étition d'un projet
name_is_required: "Le nom est requis."
illustration: "Illustration"
add_an_illustration: "Ajouter un visuel"
CAD_file: "Fichier CAO"
add_a_new_file: "Ajouter un nouveau fichier"
description_is_required: "La description est requise."
steps: "Étapes"
step_title: "Titre de l'étape"
add_a_picture: "Ajouter une image"
@ -130,10 +133,6 @@ fr:
machine:
# formulaire d'édition d'une machine
name_is_required: "Le nom est requis."
illustration: "Visuel"
add_an_illustration: "Ajouter un visuel"
description_is_required: "La description est requise."
technical_specifications_are_required: "Les caractéristiques techniques sont requises."
attached_files_(pdf): "Pièces jointes (pdf)"
attach_a_file: "Joindre un fichier"
@ -168,7 +167,6 @@ fr:
title_is_required: "Le titre est requis."
matching_visual: "Visuel associé"
choose_a_picture: "Choisir une image"
description_is_required: "La description est requise."
attachments: "Pièces jointes"
add_a_new_file: "Ajouter un nouveau fichier"
event_type: "Type d'évènement"
@ -188,7 +186,6 @@ fr:
plan:
# formulaire d'édition d'une formule d'abonnement
general_informations: "Informations générales"
name_is_required: "Le nom est requis."
name_length_must_be_less_than_24_characters: "Le nom doit faire moins de 24 caractères."
type_is_required: "Le type est requis."
group: "Groupe"
@ -213,6 +210,13 @@ fr:
new_partner: "Nouveau partenaire"
email_address_is_required: "L'adresse e-mail est requise."
trainings:
# formulaire d'édition d'une formation
add_a_new_training: "Ajouter une nouvelle formation"
validate_your_training: "Valider votre formation"
associated_machines: "Machines associées"
number_of_tickets: "Nombre de places"
user_admin:
# formulaire partiel d'édition/création utilisateur (vue admin)
group: "Groupe"

View File

@ -64,7 +64,7 @@ Rails.application.routes.draw do
resources :availabilities do
get 'machines/:machine_id', action: 'machine', on: :collection
get 'trainings', on: :collection
get 'trainings/:training_id', action: 'trainings', on: :collection
get 'reservations', on: :member
end

View File

@ -131,11 +131,11 @@ end
if Training.count == 0
Training.create!([
{name: "Formation Imprimante 3D"},
{name: "Formation Laser / Vinyle"},
{name: "Formation Petite fraiseuse numerique"},
{name: "Formation Shopbot Grande Fraiseuse"},
{name: "Formation logiciel 2D"}
{name: "Formation Imprimante 3D", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."},
{name: "Formation Laser / Vinyle", description: "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."},
{name: "Formation Petite fraiseuse numerique", description: "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."},
{name: "Formation Shopbot Grande Fraiseuse", description: "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."},
{name: "Formation logiciel 2D", description: "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo."}
])
TrainingsPricing.all.each do |p|

View File

@ -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"
}

View File

@ -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"
}

View File

@ -1,6 +1,6 @@
{
"name": "ngUpload",
"version": "0.5.17",
"version": "0.5.18",
"main": "ng-upload.js",
"ignore": [
"node_modules",

View File

@ -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;
}

View File

@ -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('<iframe name="upload-iframe-'+r+'" '+'border="0" width="0" height="0" '+'style="width:0px;height:0px;border:none;display:none">');if(d.enableRailsCsrf){var m=angular.element("<input />");m.attr("class","upload-csrf-token"),m.attr("type","hidden"),m.attr("name",a("csrf-param").attr("content")),m.val(a("csrf-token").attr("content")),o.append(m)}o.after(s),l(!1),o.bind("submit",function(n){var t=e[i.name];return t&&t.$invalid?(n.preventDefault(),!1):d.beforeSubmit&&d.beforeSubmit(e,{})===!1?(n.preventDefault(),!1):(s.bind("load",u),d.convertHidden&&angular.forEach(o.find("input"),function(n){var t=angular.element(n);t.attr("ng-model")&&t.attr("type")&&"hidden"==t.attr("type")&&t.attr("value",e.$eval(t.attr("ng-model")))}),e.$$phase?(c&&c(e),l(!0)):e.$apply(function(){c&&c(e),l(!0)}),void 0)})}}}]);
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 p(){c.unbind("load"),e.$$phase?l(!1):e.$apply(function(){l(!1)});try{var t,a=(c[0].contentDocument||c[0].contentWindow.document).body;try{t=angular.fromJson(a.innerText||a.textContent),e.$$phase?d(e,{content:t}):e.$apply(function(){d(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 u={},d=i.ngUpload?t(i.ngUpload):null,f=i.errorCatcher?t(i.errorCatcher):null,s=i.ngUploadLoading?t(i.ngUploadLoading):null;i.hasOwnProperty("uploadOptionsConvertHidden")&&(u.convertHidden="false"!=i.uploadOptionsConvertHidden),i.hasOwnProperty("uploadOptionsEnableRailsCsrf")&&(u.enableRailsCsrf="false"!=i.uploadOptionsEnableRailsCsrf),i.hasOwnProperty("uploadOptionsBeforeSubmit")&&(u.beforeSubmit=t(i.uploadOptionsBeforeSubmit)),o.attr({target:"upload-iframe-"+r,method:"post",enctype:"multipart/form-data",encoding:"multipart/form-data"});var c=angular.element('<iframe name="upload-iframe-'+r+'" '+'border="0" width="0" height="0" '+'style="width:0px;height:0px;border:none;display:none">');if(u.enableRailsCsrf){var m=angular.element("<input />");m.attr("class","upload-csrf-token"),m.attr("type","hidden"),m.attr("name",a("csrf-param").attr("content")),m.val(a("csrf-token").attr("content")),o.append(m)}o.after(c),l(!1),o.bind("submit",function(n){var t=e[i.name];return t&&t.$invalid?(n.preventDefault(),!1):u.beforeSubmit&&u.beforeSubmit(e,{})===!1?(e.$$phase||e.$apply(),n.preventDefault(),!1):(c.bind("load",p),u.convertHidden&&angular.forEach(o.find("input"),function(n){var t=angular.element(n);t.attr("ng-model")&&t.attr("type")&&"hidden"==t.attr("type")&&t.attr("value",e.$eval(t.attr("ng-model")))}),e.$$phase?(s&&s(e),l(!0)):e.$apply(function(){s&&s(e),l(!0)}),void 0)})}}}]);

View File

@ -133,7 +133,7 @@ angular.module('app', ['ngUpload'])
* Working in IE
In order, for ngUpload to respond correctly for IE, your server needs to return the response back as `html/text` not `application/json`
In order, for ngUpload to respond correctly for IE, your server needs to return the response back as `text/html` not `application/json`
## Directive Options