diff --git a/app/assets/javascripts/app/controllers/_application_controller_form.js.coffee b/app/assets/javascripts/app/controllers/_application_controller_form.js.coffee index d0ad04638..5c769b8df 100644 --- a/app/assets/javascripts/app/controllers/_application_controller_form.js.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller_form.js.coffee @@ -193,12 +193,6 @@ class App.ControllerForm extends App.Controller else attribute.required = '' - # set multible option - if attribute.multiple - attribute.multiple = 'multiple' - else - attribute.multiple = '' - # set autocapitalize option if attribute.autocapitalize is undefined || attribute.autocapitalize attribute.autocapitalize = '' @@ -228,932 +222,10 @@ class App.ControllerForm extends App.Controller if attribute.name of @params attribute.value = @params[attribute.name] - if attribute.tag is 'autocompletion' - if @params[ attribute.name + '_autocompletion_value_shown' ] - attribute.valueShown = @params[ attribute.name + '_autocompletion_value_shown' ] - App.Log.debug 'ControllerForm', 'formGenItem-before', attribute - # build options list based on config - @_getConfigOptionList( attribute ) - - # build options list based on relation - @_getRelationOptionList( attribute ) - - # add null selection if needed - @_addNullOption( attribute ) - - # sort attribute.options - @_sortOptions( attribute ) - - # finde selected/checked item of list - @_selectedOptions( attribute ) - - # disable item of list - @_disabledOptions( attribute ) - - # filter attributes - @_filterOption( attribute ) - - if attribute.tag is 'boolean' - - # build options list - if _.isEmpty(attribute.options) - attribute.options = [ - { name: 'yes', value: true } - { name: 'no', value: false } - ] - - # set data type - if attribute.name - attribute.name = '{boolean}' + attribute.name - - # finde selected item of list - for record in attribute.options - if record.value is attribute.value - record.selected = 'selected' - - # return item - item = $( App.view('generic/select')( attribute: attribute ) ) - - else if attribute.tag is 'active' - - # active attribute is always required - attribute.null = false - - # build options list - attribute.options = [ - { name: 'active', value: true } - { name: 'inactive', value: false } - ] - - # set data type - if attribute.name - attribute.name = '{boolean}' + attribute.name - - # finde selected item of list - for record in attribute.options - if record.value is attribute.value - record.selected = 'selected' - - # return item - item = $( App.view('generic/select')( attribute: attribute ) ) - - # select - else if attribute.tag is 'select' - item = $( App.view('generic/select')( attribute: attribute ) ) - - # date - else if attribute.tag is 'date' - - # set data type - if attribute.name - attribute.nameRaw = attribute.name - attribute.name = '{date}' + attribute.name - if attribute.value - if typeof( attribute.value ) is 'string' - unixtime = new Date( Date.parse( "#{attribute.value}T00:00:00Z" ) ) - else - unixtime = new Date( attribute.value ) - year = unixtime.getUTCFullYear() - month = unixtime.getUTCMonth() + 1 - day = unixtime.getUTCDate() - hour = unixtime.getUTCHours() - minute = unixtime.getUTCMinutes() - item = $( App.view('generic/date')( - attribute: attribute - year: year - month: month - day: day - ) ) - - setNewTime = (diff, el, reset) -> - name = $(el).closest('.form-group').find('[data-name]').attr('data-name') - - # remove old validation - item.find('.has-error').removeClass('has-error') - item.closest('.form-group').find('.help-inline').html('') - - day = item.closest('.form-group').find("[name=\"{date}#{name}___day\"]").val() - month = item.closest('.form-group').find("[name=\"{date}#{name}___month\"]").val() - year = item.closest('.form-group').find("[name=\"{date}#{name}___year\"]").val() - format = (number) -> - if parseInt(number) < 10 - number = "0#{number}" - number - if !reset && (year isnt '' && month isnt '' && day isnt '') - time = new Date( Date.parse( "#{year}-#{format(month)}-#{format(day)}T00:00:00Z" ) ) - time.setMinutes( time.getMinutes() + diff + time.getTimezoneOffset() ) - else - time = new Date() - time.setMinutes( time.getMinutes() + diff ) - item.closest('.form-group').find("[name=\"{date}#{name}___day\"]").val( time.getDate() ) - item.closest('.form-group').find("[name=\"{date}#{name}___month\"]").val( time.getMonth()+1 ) - item.closest('.form-group').find("[name=\"{date}#{name}___year\"]").val( time.getFullYear() ) - - item.find('.js-today').bind('click', (e) -> - e.preventDefault() - setNewTime(0, @, true) - ) - item.find('.js-plus-day').bind('click', (e) -> - e.preventDefault() - setNewTime(60 * 24, @) - ) - item.find('.js-minus-day').bind('click', (e) -> - e.preventDefault() - setNewTime(-60 * 24, @) - ) - item.find('.js-plus-week').bind('click', (e) -> - e.preventDefault() - setNewTime(60 * 24 * 7, @) - ) - item.find('.js-minus-week').bind('click', (e) -> - e.preventDefault() - setNewTime(-60 * 24 * 7, @) - ) - - item.find('input').bind('keyup blur focus change', (e) -> - - # do validation - name = $(@).attr('name') - if name - fieldPrefix = name.split('___')[0] - - # remove old validation - item.find('.has-error').removeClass('has-error') - item.closest('.form-group').find('.help-inline').html('') - - day = item.closest('.form-group').find("[name=\"#{fieldPrefix}___day\"]").val() - month = item.closest('.form-group').find("[name=\"#{fieldPrefix}___month\"]").val() - year = item.closest('.form-group').find("[name=\"#{fieldPrefix}___year\"]").val() - - # validate exists - errors = {} - if !day - errors.day = 'missing' - if !month - errors.month = 'missing' - if !year - errors.year = 'missing' - - # ranges - if day - daysInMonth = 31 - if month && year - daysInMonth = new Date(year, month, 0).getDate(); - - if parseInt(day).toString() is 'NaN' - errors.day = 'invalid' - else if parseInt(day) > daysInMonth || parseInt(day) < 1 - errors.day = 'invalid' - - if month - if parseInt(month).toString() is 'NaN' - errors.month = 'invalid' - else if parseInt(month) > 12 || parseInt(month) < 1 - errors.month = 'invalid' - - if year - if parseInt(year).toString() is 'NaN' - errors.year = 'invalid' - else if parseInt(year) > 2100 || parseInt(year) < 2001 - errors.year = 'invalid' - - if !_.isEmpty(errors) - - # if field is required, if not do not show error - if year is '' && day is '' && month - if attribute.null - e.preventDefault() - e.stopPropagation() - return - else - item.closest('.form-group').find('.help-inline').text( 'is required' ) - - # show invalid options - for key, value of errors - item.closest('.form-group').addClass('has-error') - item.closest('.form-group').find("[name=\"#{fieldPrefix}___#{key}\"]").addClass('has-error') - #item.closest('.form-group').find('.help-inline').text( value ) - - e.preventDefault() - e.stopPropagation() - return - ) - - - # date - else if attribute.tag is 'datetime' - - # set data type - if attribute.name - attribute.nameRaw = attribute.name - attribute.name = '{datetime}' + attribute.name - if attribute.value - if typeof( attribute.value ) is 'string' - unixtime = new Date( Date.parse( attribute.value ) ) - else - unixtime = new Date( attribute.value ) - year = unixtime.getFullYear() - month = unixtime.getMonth() + 1 - day = unixtime.getDate() - hour = unixtime.getHours() - minute = unixtime.getMinutes() - item = $( App.view('generic/datetime')( - attribute: attribute - year: year - month: month - day: day - hour: hour - minute: minute - ) ) - - setNewTime = (diff, el, reset) -> - name = $(el).closest('.form-group').find('[data-name]').attr('data-name') - - # remove old validation - item.find('.has-error').removeClass('has-error') - item.closest('.form-group').find('.help-inline').html('') - - day = item.closest('.form-group').find("[name=\"{datetime}#{name}___day\"]").val() - month = item.closest('.form-group').find("[name=\"{datetime}#{name}___month\"]").val() - year = item.closest('.form-group').find("[name=\"{datetime}#{name}___year\"]").val() - hour = item.closest('.form-group').find("[name=\"{datetime}#{name}___hour\"]").val() - minute = item.closest('.form-group').find("[name=\"{datetime}#{name}___minute\"]").val() - format = (number) -> - if parseInt(number) < 10 - number = "0#{number}" - number - if !reset && (year isnt '' && month isnt '' && day isnt '' && hour isnt '' && day isnt '') - time = new Date( Date.parse( "#{year}-#{format(month)}-#{format(day)}T#{format(hour)}:#{format(minute)}:00Z" ) ) - time.setMinutes( time.getMinutes() + diff + time.getTimezoneOffset() ) - else - time = new Date() - time.setMinutes( time.getMinutes() + diff ) - #console.log('T', time, time.getHours(), time.getMinutes()) - item.closest('.form-group').find("[name=\"{datetime}#{name}___day\"]").val( time.getDate() ) - item.closest('.form-group').find("[name=\"{datetime}#{name}___month\"]").val( time.getMonth()+1 ) - item.closest('.form-group').find("[name=\"{datetime}#{name}___year\"]").val( time.getFullYear() ) - item.closest('.form-group').find("[name=\"{datetime}#{name}___hour\"]").val( time.getHours() ) - item.closest('.form-group').find("[name=\"{datetime}#{name}___minute\"]").val( time.getMinutes() ) - - item.find('.js-today').bind('click', (e) -> - e.preventDefault() - setNewTime(0, @, true) - ) - item.find('.js-plus-hour').bind('click', (e) -> - e.preventDefault() - setNewTime(60, @) - ) - item.find('.js-minus-hour').bind('click', (e) -> - e.preventDefault() - setNewTime(-60, @) - ) - item.find('.js-plus-day').bind('click', (e) -> - e.preventDefault() - setNewTime(60 * 24, @) - ) - item.find('.js-minus-day').bind('click', (e) -> - e.preventDefault() - setNewTime(-60 * 24, @) - ) - item.find('.js-plus-week').bind('click', (e) -> - e.preventDefault() - setNewTime(60 * 24 * 7, @) - ) - item.find('.js-minus-week').bind('click', (e) -> - e.preventDefault() - setNewTime(-60 * 24 * 7, @) - ) - - item.find('input').bind('keyup blur focus change', (e) -> - - # do validation - name = $(@).attr('name') - if name - fieldPrefix = name.split('___')[0] - - # remove old validation - item.find('.has-error').removeClass('has-error') - item.closest('.form-group').find('.help-inline').html('') - - day = item.closest('.form-group').find("[name=\"#{fieldPrefix}___day\"]").val() - month = item.closest('.form-group').find("[name=\"#{fieldPrefix}___month\"]").val() - year = item.closest('.form-group').find("[name=\"#{fieldPrefix}___year\"]").val() - hour = item.closest('.form-group').find("[name=\"#{fieldPrefix}___hour\"]").val() - minute = item.closest('.form-group').find("[name=\"#{fieldPrefix}___minute\"]").val() - - # validate exists - errors = {} - if !day - errors.day = 'missing' - if !month - errors.month = 'missing' - if !year - errors.year = 'missing' - if !hour - errors.hour = 'missing' - if !minute - errors.minute = 'missing' - - # ranges - if day - daysInMonth = 31 - if month && year - daysInMonth = new Date(year, month, 0).getDate(); - - if parseInt(day).toString() is 'NaN' - errors.day = 'invalid' - else if parseInt(day) > daysInMonth || parseInt(day) < 1 - errors.day = 'invalid' - - if month - if parseInt(month).toString() is 'NaN' - errors.month = 'invalid' - else if parseInt(month) > 12 || parseInt(month) < 1 - errors.month = 'invalid' - - if year - if parseInt(year).toString() is 'NaN' - errors.year = 'invalid' - else if parseInt(year) > 2100 || parseInt(year) < 2001 - errors.year = 'invalid' - - if hour - if parseInt(hour).toString() is 'NaN' - errors.hour = 'invalid' - else if parseInt(hour) > 23 || parseInt(hour) < 0 - errors.hour = 'invalid' - - if minute - if parseInt(minute).toString() is 'NaN' - errors.minute = 'invalid' - else if parseInt(minute) > 59 - errors.minute = 'invalid' - - if !_.isEmpty(errors) - - # if field is required, if not do not show error - if year is '' && day is '' && month is '' && hour is '' && minute is '' - if attribute.null - e.preventDefault() - e.stopPropagation() - return - else - item.closest('.form-group').find('.help-inline').text( 'is required' ) - - # show invalid options - for key, value of errors - item.closest('.form-group').addClass('has-error') - item.closest('.form-group').find("[name=\"#{fieldPrefix}___#{key}\"]").addClass('has-error') - #item.closest('.form-group').find('.help-inline').text( value ) - - e.preventDefault() - e.stopPropagation() - return - ) - - # timezone - else if attribute.tag is 'timezone' - attribute.options = [] - timezones = App.Config.get('timezones') - - # build list based on config - for timezone_value, timezone_diff of timezones - if timezone_diff > 0 - timezone_diff = '+' + timezone_diff - item = - name: "#{timezone_value} (GMT#{timezone_diff})" - value: timezone_value - attribute.options.push item - - # finde selected item of list - for record in attribute.options - if record.value is attribute.value - record.selected = 'selected' - - item = $( App.view('generic/select')( attribute: attribute ) ) - - # postmaster_match - else if attribute.tag is 'postmaster_match' - addItem = (key, displayName, el, defaultValue = '') => - add = { name: key, display: displayName, tag: 'input', null: false, default: defaultValue } - itemInput = $( @formGenItem( add ).append('' ) ) - - # remove on click - itemInput.find('.remove').bind('click', (e) -> - e.preventDefault() - key = $(e.target).closest('.form-group').find('select, input').attr('name') - return if !key - $(e.target).closest('.controls').find('.addSelection select option[value="' + key + '"]').show() - $(e.target).closest('.controls').find('.addSelection select option[value="' + key + '"]').prop('disabled', false) - $(e.target).closest('.form-group').remove() - ) - - # add new item - control = el.closest('.postmaster_match') - control.find('.list').append(itemInput) - control.find('.addSelection select').val('') - control.find('.addSelection select option[value="' + key + '"]').prop('disabled', true) - control.find('.addSelection select option[value="' + key + '"]').hide() - - # scaffold of match elements - item = $(' -
-
-
-
-
-
-
-
-
') - - # select shown attributes - loopData = [ - { - value: 'from' - name: 'From' - }, - { - value: 'to' - name: 'To' - }, - { - value: 'cc' - name: 'Cc' - }, - { - value: 'subject' - name: 'Subject' - }, - { - value: 'body' - name: 'Body' - }, - { - value: '' - name: '-' - disable: true - }, - { - value: 'x-any-recipient' - name: 'Any Recipient' - }, - { - value: '' - name: '-' - disable: true - }, - { - value: '' - name: '- ' + App.i18n.translateInline('expert settings') + ' -' - disable: true - }, - { - value: '' - name: '-' - disable: true - }, - { - value: 'x-spam-flag' - name: 'X-Spam-Flag' - }, - { - value: 'x-spam-level' - name: 'X-Spam-Level' - }, - { - value: 'x-spam-score' - name: 'X-Spam-Score' - }, - { - value: 'x-spam-status' - name: 'X-Spam-Status' - }, - { - value: 'importance' - name: 'Importance' - }, - { - value: 'x-priority' - name: 'X-Priority' - }, - - { - value: 'organization' - name: 'Organization' - }, - - { - value: 'x-original-to' - name: 'X-Original-To' - }, - { - value: 'delivered-to' - name: 'Delivered-To' - }, - { - value: 'envelope-to' - name: 'Envelope-To' - }, - { - value: 'return-path' - name: 'Return-Path' - }, - { - value: 'mailing-list' - name: 'Mailing-List' - }, - { - value: 'list-id' - name: 'List-Id' - }, - { - value: 'list-archive' - name: 'List-Archive' - }, - { - value: 'mailing-list' - name: 'Mailing-List' - }, - { - value: 'auto-submitted' - name: 'Auto-Submitted' - }, - { - value: 'x-loop' - name: 'X-Loop' - }, - ] - for listItem in loopData - listItem.value = "#{ attribute.name }::#{listItem.value}" - add = { name: '', display: '', tag: 'select', multiple: false, null: false, nulloption: true, options: loopData, translate: true, required: false } - item.find('.addSelection').append( @formGenItem( add ) ) - - # bind add click - item.find('.add').bind('click', (e) -> - e.preventDefault() - name = $(@).closest('.controls').find('.addSelection').find('select').val() - displayName = $(@).closest('.controls').find('.addSelection').find('select option:selected').html() - return if !name - addItem( name, displayName, $(@) ) - ) - - # show default values - loopDataValue = {} - if attribute.value - for key, value of attribute.value - displayName = key - for listItem in loopData - if listItem.value is "#{ attribute.name }::#{key}" - addItem( "#{ attribute.name }::#{key}", listItem.name, item.find('.add a'), value ) - - # postmaster_set - else if attribute.tag is 'postmaster_set' - addItem = (key, displayName, el, defaultValue = '') => - collection = undefined - for listItem in loopData - if listItem.value is key - collection = listItem - if collection.relation - add = { name: key, display: displayName, tag: 'select', multiple: false, null: false, nulloption: true, relation: collection.relation, translate: true, default: defaultValue } - else if collection.options - add = { name: key, display: displayName, tag: 'select', multiple: false, null: false, nulloption: true, options: collection.options, translate: true, default: defaultValue } - else - add = { name: key, display: displayName, tag: 'input', null: false, default: defaultValue } - itemInput = $( @formGenItem( add ).append('' ) ) - - # remove on click - itemInput.find('.remove').bind('click', (e) -> - e.preventDefault() - key = $(e.target).closest('.form-group').find('select, input').attr('name') - return if !key - $(e.target).closest('.controls').find('.addSelection select option[value="' + key + '"]').show() - $(e.target).closest('.controls').find('.addSelection select option[value="' + key + '"]').prop('disabled', false) - $(e.target).closest('.form-group').remove() - ) - - # add new item - control = el.closest('.perform_set') - control.find('.list').append(itemInput) - control.find('.addSelection select').val('') - control.find('.addSelection select option[value="' + key + '"]').prop('disabled', true) - control.find('.addSelection select option[value="' + key + '"]').hide() - - # scaffold of perform elements - item = $(' -
-
-
-
-
-
-
-
-
') - - - # select shown attributes - loopData = [ - { - value: 'x-zammad-ticket-priority_id' - name: 'Ticket Priority' - relation: 'TicketPriority' - }, - { - value: 'x-zammad-ticket-state_id' - name: 'Ticket State' - relation: 'TicketState' - }, - { - value: 'x-zammad-ticket-customer' - name: 'Ticket Customer' - }, - { - value: 'x-zammad-ticket-group_id' - name: 'Ticket Group' - relation: 'Group' - }, - { - value: 'x-zammad-ticket-owner' - name: 'Ticket Owner' - }, - { - value: '' - name: '-' - disable: true - }, - { - value: 'x-zammad-article-internal' - name: 'Article Internal' - options: { true: 'Yes', false: 'No'} - }, - { - value: 'x-zammad-article-type_id' - name: 'Article Type' - relation: 'TicketArticleType' - }, - { - value: 'x-zammad-article-sender_id' - name: 'Article Sender' - relation: 'TicketArticleSender' - }, - { - value: '' - name: '-' - disable: true - }, - { - value: 'x-zammad-ignore' - name: 'Ignore Message' - options: { true: 'Yes', false: 'No'} - }, - ] - for listItem in loopData - listItem.value = "#{ attribute.name }::#{listItem.value}" - add = { name: '', display: '', tag: 'select', multiple: false, null: false, nulloption: true, options: loopData, translate: true, required: false } - item.find('.addSelection').append( @formGenItem( add ) ) - - item.find('.add').bind('click', (e) -> - e.preventDefault() - name = $(@).closest('.controls').find('.addSelection').find('select').val() - displayName = $(@).closest('.controls').find('.addSelection').find('select option:selected').html() - return if !name - addItem( name, displayName, $(@) ) - ) - - # show default values - loopDataValue = {} - if attribute.value - for key, value of attribute.value - displayName = key - for listItem in loopData - if listItem.value is "#{ attribute.name }::#{key}" - addItem( "#{ attribute.name }::#{key}", listItem.name, item.find('.add a'), value ) - - # checkbox - else if attribute.tag is 'checkbox' - item = $( App.view('generic/checkbox')( attribute: attribute ) ) - - # radio - else if attribute.tag is 'radio' - item = $( App.view('generic/radio')( attribute: attribute ) ) - - # richtext - else if attribute.tag is 'richtext' - item = $( App.view('generic/richtext')( attribute: attribute ) ) - item.find('[contenteditable]').ce( - mode: attribute.type - maxlength: attribute.maxlength - ) - if attribute.upload - item.append( $( App.view('generic/attachment')( attribute: attribute ) ) ) - - renderAttachment = (file) => - item.find('.attachments').append( App.view('generic/attachment_item')( - fileName: file.filename - fileSize: @humanFileSize( file.size ) - store_id: file.store_id - )) - item.on( - 'click' - "[data-id=#{file.store_id}]", (e) => - @attachments = _.filter( - @attachments, - (item) -> - return if item.id isnt file.store_id - item - ) - store_id = $(e.currentTarget).data('id') - - # delete attachment from storage - App.Ajax.request( - type: 'DELETE' - url: App.Config.get('api_path') + '/ticket_attachment_upload' - data: JSON.stringify( { store_id: store_id } ), - processData: false - success: (data, status, xhr) => - ) - - # remove attachment from dom - element = $(e.currentTarget).closest('.attachments') - $(e.currentTarget).closest('.attachment').remove() - # empty .attachment (remove spaces) to keep css working, thanks @mrflix :-o - if element.find('.attachment').length == 0 - element.empty() - ) - - @attachments = [] - @progressBar = item.find('.attachmentUpload-progressBar') - @progressText = item.find('.js-percentage') - @attachmentPlaceholder = item.find('.attachmentPlaceholder') - @attachmentUpload = item.find('.attachmentUpload') - @attachmentsHolder = item.find('.attachments') - @cancelContainer = item.find('.js-cancel') - - u = => html5Upload.initialize( - uploadUrl: App.Config.get('api_path') + '/ticket_attachment_upload', - dropContainer: item.closest('form').get(0), - cancelContainer: @cancelContainer, - inputField: item.find( 'input' ).get(0), - key: 'File', - data: { form_id: @form_id }, - maxSimultaneousUploads: 1, - onFileAdded: (file) => - - file.on( - - onStart: => - @attachmentPlaceholder.addClass('hide') - @attachmentUpload.removeClass('hide') - @cancelContainer.removeClass('hide') - console.log('upload start') - - onAborted: => - @attachmentPlaceholder.removeClass('hide') - @attachmentUpload.addClass('hide') - - # Called after received response from the server - onCompleted: (response) => - - response = JSON.parse(response) - @attachments.push response.data - - @attachmentPlaceholder.removeClass('hide') - @attachmentUpload.addClass('hide') - - renderAttachment(response.data) - console.log('upload complete', response.data ) - - # Called during upload progress, first parameter - # is decimal value from 0 to 100. - onProgress: (progress, fileSize, uploadedBytes) => - @progressBar.width(parseInt(progress) + "%") - @progressText.text(parseInt(progress)) - # hide cancel on 90% - if parseInt(progress) >= 90 - @cancelContainer.addClass('hide') - console.log('uploadProgress ', parseInt(progress)) - ) - ) - App.Delay.set( u, 100, undefined, 'form_upload' ) - - # textarea - else if attribute.tag is 'textarea' - fileUploaderId = 'file-uploader-' + new Date().getTime() + '-' + Math.floor( Math.random() * 99999 ) - item = $( App.view('generic/textarea')( attribute: attribute ) + '
' ) - - a = => - visible = $( item[0] ).is(":visible") - if visible && !$( item[0] ).expanding('active') - $( item[0] ).expanding() - $( item[0] ).on('focus', -> - visible = $( item[0] ).is(":visible") - if visible && !$( item[0] ).expanding('active') - $( item[0] ).expanding().focus() - ) - App.Delay.set( a, 80 ) - - if attribute.upload - - # add file uploader - u = => - # only add upload item if html element exists - if @el.find('#' + fileUploaderId )[0] - @el.find('#' + fileUploaderId ).fineUploader( - request: - endpoint: App.Config.get('api_path') + '/ticket_attachment_upload' - params: - form_id: @form_id - text: - uploadButton: '' - template: '
' + - '
{dragZoneText}
' + - '
{uploadButtonText}
' + - '' + - '
', - classes: - success: '' - fail: '' - debug: false - ) - App.Delay.set( u, 100, undefined, 'form_upload' ) - - # article - else if attribute.tag is 'article' - item = $( App.view('generic/article')( attribute: attribute ) ) - - # tag - else if attribute.tag is 'tag' - item = $( App.view('generic/input')( attribute: attribute ) ) - a = => - $('#' + attribute.id ).tokenfield() - $('#' + attribute.id ).parent().css('height', 'auto') - App.Delay.set( a, 120, undefined, 'tags' ) - - # user - else if attribute.tag is 'user_autocompletion' - completion = new App.UserOrganizationAutocompletion( attribute: attribute ) - item = completion.element() - - # searchable select - else if attribute.tag is 'searchable_select' - select = new App.SearchableSelect( attribute: attribute ) - item = select.element() - - # autocompletion - else if attribute.tag is 'autocompletion' - item = $( App.view('generic/autocompletion')( attribute: attribute ) ) - - a = => - local_attribute = '#' + attribute.id - local_attribute_full = '#' + attribute.id + '_autocompletion' - @callback = attribute.callback - - # call calback on init - if @callback && attribute.value && @params - @callback( @params ) - - b = (event, item) => - # set html form attribute - $(local_attribute).val(item.id).trigger('change') - $(local_attribute + '_autocompletion_value_shown').val(item.value) - - # call calback - if @callback - params = App.ControllerForm.params(form) - @callback( params ) - ### - $(@local_attribute_full).tagsInput( - autocomplete_url: '/users/search', - height: '30px', - width: '530px', - auto: { - source: '/users/search', - minLength: 2, - select: ( event, ui ) -> - #@log 'notice', 'selected', event, ui - b(event, ui.item) - } - ) - ### - source = attribute.source - if typeof(source) is 'string' - source = source.replace('#{@apiPath}', App.Config.get('api_path') ); - $(local_attribute_full).autocomplete( - source: source, - minLength: attribute.minLengt || 3, - select: ( event, ui ) => - b(event, ui.item) - ) - App.Delay.set( a, 280, undefined, 'form_autocompletion' ) - - # working_hour - else if attribute.tag is 'working_hour' - if !attribute.value - attribute.value = {} - item = $( App.view('generic/working_hour')( attribute: attribute ) ) + if App.UiElement[attribute.tag] + item = App.UiElement[attribute.tag].render(attribute, @params, @) # working_hour else if attribute.tag is 'time_before_last' @@ -2090,226 +1162,6 @@ class App.ControllerForm extends App.Controller else ui._optional(attribute.name) - # sort attribute.options - _sortOptions: (attribute) -> - - # skip sorting if it is disabled by config - return if attribute.sortBy == null - - return if !attribute.options - - if _.isArray( attribute.options ) - # reverse if we have to exit early, if configured - if attribute.order - if attribute.order == 'DESC' - attribute.options = attribute.options.reverse() - return - - options_by_name = [] - for i in attribute.options - options_by_name.push i['name'].toString().toLowerCase() - options_by_name = options_by_name.sort() - - options_new = [] - options_new_used = {} - for i in options_by_name - for ii, vv in attribute.options - if !options_new_used[ ii['value'] ] && i.toString().toLowerCase() is ii['name'].toString().toLowerCase() - options_new_used[ ii['value'] ] = 1 - options_new.push ii - attribute.options = options_new - - # do a final reverse, if configured - if attribute.order - if attribute.order == 'DESC' - attribute.options = attribute.options.reverse() - - _addNullOption: (attribute) -> - return if !attribute.options - return if !attribute.nulloption - if _.isArray( attribute.options ) - attribute.options.unshift( { name: '-', value: '' } ) - else - attribute.options[''] = '-' - - _getConfigOptionList: (attribute) -> - return if !attribute.options - selection = attribute.options - attribute.options = [] - if _.isArray( selection ) - for row in selection - if attribute.translate - row.name = App.i18n.translateInline( row.name ) - attribute.options.push row - else - order = _.sortBy( - _.keys(selection), (item) -> - selection[item].toString().toLowerCase() - ) - for key in order - name_new = selection[key] - if attribute.translate - name_new = App.i18n.translateInline( name_new ) - attribute.options.push { - name: name_new - value: key - } - - _getRelationOptionList: (attribute) -> - - # build options list based on relation - return if !attribute.relation - return if !App[attribute.relation] - - attribute.options = [] - list = [] - if attribute.filter - - App.Log.debug 'ControllerForm', '_getRelationOptionList:filter', attribute.filter - - # function based filter - if typeof attribute.filter is 'function' - App.Log.debug 'ControllerForm', '_getRelationOptionList:filter-function' - - all = App[ attribute.relation ].search( sortBy: attribute.sortBy ) - - list = attribute.filter( all, 'collection', @params ) - - # data based filter - else if attribute.filter[ attribute.name ] - filter = attribute.filter[ attribute.name ] - - App.Log.debug 'ControllerForm', '_getRelationOptionList:filter-data', filter - - # check all records - for record in App[ attribute.relation ].search( sortBy: attribute.sortBy ) - - # check all filter attributes - for key in filter - - # check all filter values as array - # if it's matching, use it for selection - if record['id'] is key - list.push record - - # data based filter - else if attribute.filter && _.isArray attribute.filter - - App.Log.debug 'ControllerForm', '_getRelationOptionList:filter-array', attribute.filter - - # check all records - for record in App[ attribute.relation ].search( sortBy: attribute.sortBy ) - - # check all filter attributes - for key in attribute.filter - - # check all filter values as array - # if it's matching, use it for selection - if record['id'] is key || ( record['id'] && key && record['id'].toString() is key.toString() ) - list.push record - - # check if current value need to be added - if @params[ attribute.name ] - hit = false - for value in list - if value['id'].toString() is @params[ attribute.name ].toString() - hit = true - if !hit - currentRecord = App[ attribute.relation ].find( @params[ attribute.name ] ) - list.push currentRecord - - # no data filter matched - else - App.Log.debug 'ControllerForm', '_getRelationOptionList:filter-data no filter matched' - list = App[ attribute.relation ].search( sortBy: attribute.sortBy ) - else - App.Log.debug 'ControllerForm', '_getRelationOptionList:filter-no filter defined' - list = App[ attribute.relation ].search( sortBy: attribute.sortBy ) - - App.Log.debug 'ControllerForm', '_getRelationOptionList', attribute, list - - # build options list - @_buildOptionList( list, attribute ) - - # build options list - _buildOptionList: (list, attribute) -> - - for item in list - - # if active or if active doesn't exist - if item.active || !( 'active' of item ) - name_new = '?' - if item.displayName - name_new = item.displayName() - else if item.name - name_new = item.name - if attribute.translate - name_new = App.i18n.translateInline(name_new) - attribute.options.push { - name: name_new, - value: item.id, - note: item.note, - } - - # execute filter - _filterOption: (attribute) -> - return if !attribute.filter - return if !attribute.options - - return if typeof attribute.filter isnt 'function' - App.Log.debug 'ControllerForm', '_filterOption:filter-function' - - attribute.options = attribute.filter( attribute.options, attribute ) - - # set selected attributes - _selectedOptions: (attribute) -> - return if !attribute.options - - # check if selected / checked need to be set - check = (value, record) -> - if typeof value is 'string' || typeof value is 'number' || typeof value is 'boolean' - - # if name or value is matching - if record.value.toString() is value.toString() || record.name.toString() is value.toString() - record.selected = 'selected' - record.checked = 'checked' - - else if ( value && record.value && _.include( value, record.value ) ) || ( value && record.name && _.include( value, record.name ) ) - record.selected = 'selected' - record.checked = 'checked' - - # lookup of any record - for record in attribute.options - - if _.isArray( attribute.value ) - for value in attribute.value - check( value, record ) - - if typeof attribute.value is 'string' || typeof attribute.value is 'number' || typeof attribute.value is 'boolean' - check( attribute.value, record ) - - # if noting is selected / checked, use default as selected / checked - selected = false - for record in attribute.options - if record.selected || record.checked - selected = true - if !selected - for record in attribute.options - if typeof attribute.default is 'string' || typeof attribute.default is 'number' || typeof attribute.default is 'boolean' - check( attribute.default, record ) - - # set disabled attributes - _disabledOptions: (attribute) -> - - return if !attribute.options - return if !_.isArray( attribute.options ) - - for record in attribute.options - if record.disable is true - record.disabled = 'disabled' - else - record.disabled = '' - validate: (params) -> App.Model.validate( model: @model diff --git a/app/assets/javascripts/app/controllers/_ui_element/_application_ui_element.js.coffee b/app/assets/javascripts/app/controllers/_ui_element/_application_ui_element.js.coffee new file mode 100644 index 000000000..909e6edf8 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_ui_element/_application_ui_element.js.coffee @@ -0,0 +1,221 @@ +class App.UiElement.ApplicationUiElement + + # sort attribute.options + @sortOptions: (attribute) -> + + # skip sorting if it is disabled by config + return if attribute.sortBy == null + + return if !attribute.options + + if _.isArray( attribute.options ) + # reverse if we have to exit early, if configured + if attribute.order + if attribute.order == 'DESC' + attribute.options = attribute.options.reverse() + return + + options_by_name = [] + for i in attribute.options + options_by_name.push i['name'].toString().toLowerCase() + options_by_name = options_by_name.sort() + + options_new = [] + options_new_used = {} + for i in options_by_name + for ii, vv in attribute.options + if !options_new_used[ ii['value'] ] && i.toString().toLowerCase() is ii['name'].toString().toLowerCase() + options_new_used[ ii['value'] ] = 1 + options_new.push ii + attribute.options = options_new + + # do a final reverse, if configured + if attribute.order + if attribute.order == 'DESC' + attribute.options = attribute.options.reverse() + + @addNullOption: (attribute) -> + return if !attribute.options + return if !attribute.nulloption + if _.isArray( attribute.options ) + attribute.options.unshift( { name: '-', value: '' } ) + else + attribute.options[''] = '-' + + @getConfigOptionList: (attribute) -> + return if !attribute.options + selection = attribute.options + attribute.options = [] + if _.isArray( selection ) + for row in selection + if attribute.translate + row.name = App.i18n.translateInline( row.name ) + attribute.options.push row + else + order = _.sortBy( + _.keys(selection), (item) -> + selection[item].toString().toLowerCase() + ) + for key in order + name_new = selection[key] + if attribute.translate + name_new = App.i18n.translateInline( name_new ) + attribute.options.push { + name: name_new + value: key + } + + @getRelationOptionList: (attribute, params) -> + + # build options list based on relation + return if !attribute.relation + return if !App[attribute.relation] + + attribute.options = [] + list = [] + if attribute.filter + + App.Log.debug 'ControllerForm', '_getRelationOptionList:filter', attribute.filter + + # function based filter + if typeof attribute.filter is 'function' + App.Log.debug 'ControllerForm', '_getRelationOptionList:filter-function' + + all = App[ attribute.relation ].search( sortBy: attribute.sortBy ) + + list = attribute.filter( all, 'collection', params ) + + # data based filter + else if attribute.filter[ attribute.name ] + filter = attribute.filter[ attribute.name ] + + App.Log.debug 'ControllerForm', '_getRelationOptionList:filter-data', filter + + # check all records + for record in App[ attribute.relation ].search( sortBy: attribute.sortBy ) + + # check all filter attributes + for key in filter + + # check all filter values as array + # if it's matching, use it for selection + if record['id'] is key + list.push record + + # data based filter + else if attribute.filter && _.isArray attribute.filter + + App.Log.debug 'ControllerForm', '_getRelationOptionList:filter-array', attribute.filter + + # check all records + for record in App[ attribute.relation ].search( sortBy: attribute.sortBy ) + + # check all filter attributes + for key in attribute.filter + + # check all filter values as array + # if it's matching, use it for selection + if record['id'] is key || ( record['id'] && key && record['id'].toString() is key.toString() ) + list.push record + + # check if current value need to be added + if params[ attribute.name ] + hit = false + for value in list + if value['id'].toString() is params[ attribute.name ].toString() + hit = true + if !hit + currentRecord = App[ attribute.relation ].find( params[ attribute.name ] ) + list.push currentRecord + + # no data filter matched + else + App.Log.debug 'ControllerForm', '_getRelationOptionList:filter-data no filter matched' + list = App[ attribute.relation ].search( sortBy: attribute.sortBy ) + else + App.Log.debug 'ControllerForm', '_getRelationOptionList:filter-no filter defined' + list = App[ attribute.relation ].search( sortBy: attribute.sortBy ) + + App.Log.debug 'ControllerForm', '_getRelationOptionList', attribute, list + + # build options list + @buildOptionList( list, attribute ) + + # build options list + @buildOptionList: (list, attribute) -> + + for item in list + + # if active or if active doesn't exist + if item.active || !( 'active' of item ) + name_new = '?' + if item.displayName + name_new = item.displayName() + else if item.name + name_new = item.name + if attribute.translate + name_new = App.i18n.translateInline(name_new) + attribute.options.push { + name: name_new, + value: item.id, + note: item.note, + } + + # execute filter + @filterOption: (attribute) -> + return if !attribute.filter + return if !attribute.options + + return if typeof attribute.filter isnt 'function' + App.Log.debug 'ControllerForm', '_filterOption:filter-function' + + attribute.options = attribute.filter( attribute.options, attribute ) + + # set selected attributes + @selectedOptions: (attribute) -> + return if !attribute.options + + # check if selected / checked need to be set + check = (value, record) -> + if typeof value is 'string' || typeof value is 'number' || typeof value is 'boolean' + + # if name or value is matching + if record.value.toString() is value.toString() || record.name.toString() is value.toString() + record.selected = 'selected' + record.checked = 'checked' + + else if ( value && record.value && _.include( value, record.value ) ) || ( value && record.name && _.include( value, record.name ) ) + record.selected = 'selected' + record.checked = 'checked' + + # lookup of any record + for record in attribute.options + + if _.isArray( attribute.value ) + for value in attribute.value + check( value, record ) + + if typeof attribute.value is 'string' || typeof attribute.value is 'number' || typeof attribute.value is 'boolean' + check( attribute.value, record ) + + # if noting is selected / checked, use default as selected / checked + selected = false + for record in attribute.options + if record.selected || record.checked + selected = true + if !selected + for record in attribute.options + if typeof attribute.default is 'string' || typeof attribute.default is 'number' || typeof attribute.default is 'boolean' + check( attribute.default, record ) + + # set disabled attributes + @disabledOptions: (attribute) -> + + return if !attribute.options + return if !_.isArray( attribute.options ) + + for record in attribute.options + if record.disable is true + record.disabled = 'disabled' + else + record.disabled = '' diff --git a/app/assets/javascripts/app/controllers/_ui_element/active.js.coffee b/app/assets/javascripts/app/controllers/_ui_element/active.js.coffee new file mode 100644 index 000000000..820a76fd1 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_ui_element/active.js.coffee @@ -0,0 +1,28 @@ +class App.UiElement.active extends App.UiElement.ApplicationUiElement + @render: (attribute, params) -> + + # set attributes + attribute.null = false + attribute.translation = true + + # build options list + attribute.options = [ + { name: 'active', value: true } + { name: 'inactive', value: false } + ] + + # set data type + if attribute.name + attribute.name = '{boolean}' + attribute.name + + # build options list based on config + @getConfigOptionList( attribute, params ) + + # sort attribute.options + @sortOptions( attribute, params ) + + # finde selected/checked item of list + @selectedOptions( attribute, params ) + + # return item + $( App.view('generic/select')( attribute: attribute ) ) \ No newline at end of file diff --git a/app/assets/javascripts/app/controllers/_ui_element/autocompletion.js.coffee b/app/assets/javascripts/app/controllers/_ui_element/autocompletion.js.coffee new file mode 100644 index 000000000..65d11b1c0 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_ui_element/autocompletion.js.coffee @@ -0,0 +1,51 @@ +class App.UiElement.autocompletion + @render: (attribute, params) -> + + if params[ attribute.name + '_autocompletion_value_shown' ] + attribute.valueShown = params[ attribute.name + '_autocompletion_value_shown' ] + + item = $( App.view('generic/autocompletion')( attribute: attribute ) ) + + a = => + local_attribute = '#' + attribute.id + local_attribute_full = '#' + attribute.id + '_autocompletion' + @callback = attribute.callback + + # call calback on init + if @callback && attribute.value && @params + @callback( @params ) + + b = (event, item) => + # set html form attribute + $(local_attribute).val(item.id).trigger('change') + $(local_attribute + '_autocompletion_value_shown').val(item.value) + + # call calback + if @callback + params = App.ControllerForm.params(form) + @callback( params ) + ### + $(@local_attribute_full).tagsInput( + autocomplete_url: '/users/search', + height: '30px', + width: '530px', + auto: { + source: '/users/search', + minLength: 2, + select: ( event, ui ) -> + #@log 'notice', 'selected', event, ui + b(event, ui.item) + } + ) + ### + source = attribute.source + if typeof(source) is 'string' + source = source.replace('#{@apiPath}', App.Config.get('api_path') ); + $(local_attribute_full).autocomplete( + source: source, + minLength: attribute.minLengt || 3, + select: ( event, ui ) => + b(event, ui.item) + ) + App.Delay.set( a, 280, undefined, 'form_autocompletion' ) + item diff --git a/app/assets/javascripts/app/controllers/_ui_element/boolean.js.coffee b/app/assets/javascripts/app/controllers/_ui_element/boolean.js.coffee new file mode 100644 index 000000000..c201e6bea --- /dev/null +++ b/app/assets/javascripts/app/controllers/_ui_element/boolean.js.coffee @@ -0,0 +1,25 @@ +class App.UiElement.boolean extends App.UiElement.ApplicationUiElement + @render: (attribute, params) -> + + # build options list + if _.isEmpty(attribute.options) + attribute.options = [ + { name: 'yes', value: true } + { name: 'no', value: false } + ] + + # set data type + if attribute.name + attribute.name = '{boolean}' + attribute.name + + # build options list based on config + @getConfigOptionList( attribute, params ) + + # sort attribute.options + @sortOptions( attribute, params ) + + # finde selected/checked item of list + @selectedOptions( attribute, params ) + + # return item + $( App.view('generic/select')( attribute: attribute ) ) \ No newline at end of file diff --git a/app/assets/javascripts/app/controllers/_ui_element/checkbox.js.coffee b/app/assets/javascripts/app/controllers/_ui_element/checkbox.js.coffee new file mode 100644 index 000000000..5f16de1eb --- /dev/null +++ b/app/assets/javascripts/app/controllers/_ui_element/checkbox.js.coffee @@ -0,0 +1,25 @@ +class App.UiElement.checkbox extends App.UiElement.ApplicationUiElement + @render: (attribute, params) -> + + # build options list based on config + @getConfigOptionList( attribute, params ) + + # build options list based on relation + @getRelationOptionList( attribute, params ) + + # add null selection if needed + @addNullOption( attribute, params ) + + # sort attribute.options + @sortOptions( attribute, params ) + + # finde selected/checked item of list + @selectedOptions( attribute, params ) + + # disable item of list + @disabledOptions( attribute, params ) + + # filter attributes + @filterOption( attribute, params ) + + $( App.view('generic/checkbox')( attribute: attribute ) ) diff --git a/app/assets/javascripts/app/controllers/_ui_element/date.js.coffee b/app/assets/javascripts/app/controllers/_ui_element/date.js.coffee new file mode 100644 index 000000000..f8b9db2ac --- /dev/null +++ b/app/assets/javascripts/app/controllers/_ui_element/date.js.coffee @@ -0,0 +1,139 @@ +class App.UiElement.date + @render: (attribute) -> + + # set data type + if attribute.name + attribute.nameRaw = attribute.name + attribute.name = '{date}' + attribute.name + if attribute.value + if typeof( attribute.value ) is 'string' + unixtime = new Date( Date.parse( "#{attribute.value}T00:00:00Z" ) ) + else + unixtime = new Date( attribute.value ) + year = unixtime.getUTCFullYear() + month = unixtime.getUTCMonth() + 1 + day = unixtime.getUTCDate() + hour = unixtime.getUTCHours() + minute = unixtime.getUTCMinutes() + item = $( App.view('generic/date')( + attribute: attribute + year: year + month: month + day: day + ) ) + + setNewTime = (diff, el, reset) -> + name = $(el).closest('.form-group').find('[data-name]').attr('data-name') + + # remove old validation + item.find('.has-error').removeClass('has-error') + item.closest('.form-group').find('.help-inline').html('') + + day = item.closest('.form-group').find("[name=\"{date}#{name}___day\"]").val() + month = item.closest('.form-group').find("[name=\"{date}#{name}___month\"]").val() + year = item.closest('.form-group').find("[name=\"{date}#{name}___year\"]").val() + format = (number) -> + if parseInt(number) < 10 + number = "0#{number}" + number + if !reset && (year isnt '' && month isnt '' && day isnt '') + time = new Date( Date.parse( "#{year}-#{format(month)}-#{format(day)}T00:00:00Z" ) ) + time.setMinutes( time.getMinutes() + diff + time.getTimezoneOffset() ) + else + time = new Date() + time.setMinutes( time.getMinutes() + diff ) + item.closest('.form-group').find("[name=\"{date}#{name}___day\"]").val( time.getDate() ) + item.closest('.form-group').find("[name=\"{date}#{name}___month\"]").val( time.getMonth()+1 ) + item.closest('.form-group').find("[name=\"{date}#{name}___year\"]").val( time.getFullYear() ) + + item.find('.js-today').bind('click', (e) -> + e.preventDefault() + setNewTime(0, @, true) + ) + item.find('.js-plus-day').bind('click', (e) -> + e.preventDefault() + setNewTime(60 * 24, @) + ) + item.find('.js-minus-day').bind('click', (e) -> + e.preventDefault() + setNewTime(-60 * 24, @) + ) + item.find('.js-plus-week').bind('click', (e) -> + e.preventDefault() + setNewTime(60 * 24 * 7, @) + ) + item.find('.js-minus-week').bind('click', (e) -> + e.preventDefault() + setNewTime(-60 * 24 * 7, @) + ) + + item.find('input').bind('keyup blur focus change', (e) -> + + # do validation + name = $(@).attr('name') + if name + fieldPrefix = name.split('___')[0] + + # remove old validation + item.find('.has-error').removeClass('has-error') + item.closest('.form-group').find('.help-inline').html('') + + day = item.closest('.form-group').find("[name=\"#{fieldPrefix}___day\"]").val() + month = item.closest('.form-group').find("[name=\"#{fieldPrefix}___month\"]").val() + year = item.closest('.form-group').find("[name=\"#{fieldPrefix}___year\"]").val() + + # validate exists + errors = {} + if !day + errors.day = 'missing' + if !month + errors.month = 'missing' + if !year + errors.year = 'missing' + + # ranges + if day + daysInMonth = 31 + if month && year + daysInMonth = new Date(year, month, 0).getDate(); + + if parseInt(day).toString() is 'NaN' + errors.day = 'invalid' + else if parseInt(day) > daysInMonth || parseInt(day) < 1 + errors.day = 'invalid' + + if month + if parseInt(month).toString() is 'NaN' + errors.month = 'invalid' + else if parseInt(month) > 12 || parseInt(month) < 1 + errors.month = 'invalid' + + if year + if parseInt(year).toString() is 'NaN' + errors.year = 'invalid' + else if parseInt(year) > 2100 || parseInt(year) < 2001 + errors.year = 'invalid' + + if !_.isEmpty(errors) + + # if field is required, if not do not show error + if year is '' && day is '' && month + if attribute.null + e.preventDefault() + e.stopPropagation() + return + else + item.closest('.form-group').find('.help-inline').text( 'is required' ) + + # show invalid options + for key, value of errors + item.closest('.form-group').addClass('has-error') + item.closest('.form-group').find("[name=\"#{fieldPrefix}___#{key}\"]").addClass('has-error') + #item.closest('.form-group').find('.help-inline').text( value ) + + e.preventDefault() + e.stopPropagation() + return + ) + + item \ No newline at end of file diff --git a/app/assets/javascripts/app/controllers/_ui_element/datetime.js.coffee b/app/assets/javascripts/app/controllers/_ui_element/datetime.js.coffee new file mode 100644 index 000000000..e806b76f4 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_ui_element/datetime.js.coffee @@ -0,0 +1,172 @@ +class App.UiElement.datetime + @render: (attribute) -> + + # set data type + if attribute.name + attribute.nameRaw = attribute.name + attribute.name = '{datetime}' + attribute.name + if attribute.value + if typeof( attribute.value ) is 'string' + unixtime = new Date( Date.parse( attribute.value ) ) + else + unixtime = new Date( attribute.value ) + year = unixtime.getFullYear() + month = unixtime.getMonth() + 1 + day = unixtime.getDate() + hour = unixtime.getHours() + minute = unixtime.getMinutes() + item = $( App.view('generic/datetime')( + attribute: attribute + year: year + month: month + day: day + hour: hour + minute: minute + ) ) + + setNewTime = (diff, el, reset) -> + name = $(el).closest('.form-group').find('[data-name]').attr('data-name') + + # remove old validation + item.find('.has-error').removeClass('has-error') + item.closest('.form-group').find('.help-inline').html('') + + day = item.closest('.form-group').find("[name=\"{datetime}#{name}___day\"]").val() + month = item.closest('.form-group').find("[name=\"{datetime}#{name}___month\"]").val() + year = item.closest('.form-group').find("[name=\"{datetime}#{name}___year\"]").val() + hour = item.closest('.form-group').find("[name=\"{datetime}#{name}___hour\"]").val() + minute = item.closest('.form-group').find("[name=\"{datetime}#{name}___minute\"]").val() + format = (number) -> + if parseInt(number) < 10 + number = "0#{number}" + number + if !reset && (year isnt '' && month isnt '' && day isnt '' && hour isnt '' && day isnt '') + time = new Date( Date.parse( "#{year}-#{format(month)}-#{format(day)}T#{format(hour)}:#{format(minute)}:00Z" ) ) + time.setMinutes( time.getMinutes() + diff + time.getTimezoneOffset() ) + else + time = new Date() + time.setMinutes( time.getMinutes() + diff ) + #console.log('T', time, time.getHours(), time.getMinutes()) + item.closest('.form-group').find("[name=\"{datetime}#{name}___day\"]").val( time.getDate() ) + item.closest('.form-group').find("[name=\"{datetime}#{name}___month\"]").val( time.getMonth()+1 ) + item.closest('.form-group').find("[name=\"{datetime}#{name}___year\"]").val( time.getFullYear() ) + item.closest('.form-group').find("[name=\"{datetime}#{name}___hour\"]").val( time.getHours() ) + item.closest('.form-group').find("[name=\"{datetime}#{name}___minute\"]").val( time.getMinutes() ) + + item.find('.js-today').bind('click', (e) -> + e.preventDefault() + setNewTime(0, @, true) + ) + item.find('.js-plus-hour').bind('click', (e) -> + e.preventDefault() + setNewTime(60, @) + ) + item.find('.js-minus-hour').bind('click', (e) -> + e.preventDefault() + setNewTime(-60, @) + ) + item.find('.js-plus-day').bind('click', (e) -> + e.preventDefault() + setNewTime(60 * 24, @) + ) + item.find('.js-minus-day').bind('click', (e) -> + e.preventDefault() + setNewTime(-60 * 24, @) + ) + item.find('.js-plus-week').bind('click', (e) -> + e.preventDefault() + setNewTime(60 * 24 * 7, @) + ) + item.find('.js-minus-week').bind('click', (e) -> + e.preventDefault() + setNewTime(-60 * 24 * 7, @) + ) + + item.find('input').bind('keyup blur focus change', (e) -> + + # do validation + name = $(@).attr('name') + if name + fieldPrefix = name.split('___')[0] + + # remove old validation + item.find('.has-error').removeClass('has-error') + item.closest('.form-group').find('.help-inline').html('') + + day = item.closest('.form-group').find("[name=\"#{fieldPrefix}___day\"]").val() + month = item.closest('.form-group').find("[name=\"#{fieldPrefix}___month\"]").val() + year = item.closest('.form-group').find("[name=\"#{fieldPrefix}___year\"]").val() + hour = item.closest('.form-group').find("[name=\"#{fieldPrefix}___hour\"]").val() + minute = item.closest('.form-group').find("[name=\"#{fieldPrefix}___minute\"]").val() + + # validate exists + errors = {} + if !day + errors.day = 'missing' + if !month + errors.month = 'missing' + if !year + errors.year = 'missing' + if !hour + errors.hour = 'missing' + if !minute + errors.minute = 'missing' + + # ranges + if day + daysInMonth = 31 + if month && year + daysInMonth = new Date(year, month, 0).getDate(); + + if parseInt(day).toString() is 'NaN' + errors.day = 'invalid' + else if parseInt(day) > daysInMonth || parseInt(day) < 1 + errors.day = 'invalid' + + if month + if parseInt(month).toString() is 'NaN' + errors.month = 'invalid' + else if parseInt(month) > 12 || parseInt(month) < 1 + errors.month = 'invalid' + + if year + if parseInt(year).toString() is 'NaN' + errors.year = 'invalid' + else if parseInt(year) > 2100 || parseInt(year) < 2001 + errors.year = 'invalid' + + if hour + if parseInt(hour).toString() is 'NaN' + errors.hour = 'invalid' + else if parseInt(hour) > 23 || parseInt(hour) < 0 + errors.hour = 'invalid' + + if minute + if parseInt(minute).toString() is 'NaN' + errors.minute = 'invalid' + else if parseInt(minute) > 59 + errors.minute = 'invalid' + + if !_.isEmpty(errors) + + # if field is required, if not do not show error + if year is '' && day is '' && month is '' && hour is '' && minute is '' + if attribute.null + e.preventDefault() + e.stopPropagation() + return + else + item.closest('.form-group').find('.help-inline').text( 'is required' ) + + # show invalid options + for key, value of errors + item.closest('.form-group').addClass('has-error') + item.closest('.form-group').find("[name=\"#{fieldPrefix}___#{key}\"]").addClass('has-error') + #item.closest('.form-group').find('.help-inline').text( value ) + + e.preventDefault() + e.stopPropagation() + return + ) + + item diff --git a/app/assets/javascripts/app/controllers/_ui_element/postmaster_match.js.coffee b/app/assets/javascripts/app/controllers/_ui_element/postmaster_match.js.coffee new file mode 100644 index 000000000..2065ffb86 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_ui_element/postmaster_match.js.coffee @@ -0,0 +1,176 @@ +class App.UiElement.postmaster_match + @render: (attribute, params, form_controller) -> + addItem = (key, displayName, el, defaultValue = '') => + add = { name: key, display: displayName, tag: 'input', null: false, default: defaultValue } + itemInput = $( form_controller.formGenItem( add ).append('' ) ) + + # remove on click + itemInput.find('.remove').bind('click', (e) -> + e.preventDefault() + key = $(e.target).closest('.form-group').find('select, input').attr('name') + return if !key + $(e.target).closest('.controls').find('.addSelection select option[value="' + key + '"]').show() + $(e.target).closest('.controls').find('.addSelection select option[value="' + key + '"]').prop('disabled', false) + $(e.target).closest('.form-group').remove() + ) + + # add new item + control = el.closest('.postmaster_match') + control.find('.list').append(itemInput) + control.find('.addSelection select').val('') + control.find('.addSelection select option[value="' + key + '"]').prop('disabled', true) + control.find('.addSelection select option[value="' + key + '"]').hide() + + # scaffold of match elements + item = $(' +
+
+
+
+
+
+ +
+
') + + # select shown attributes + loopData = [ + { + value: 'from' + name: 'From' + }, + { + value: 'to' + name: 'To' + }, + { + value: 'cc' + name: 'Cc' + }, + { + value: 'subject' + name: 'Subject' + }, + { + value: 'body' + name: 'Body' + }, + { + value: '' + name: '-' + disable: true + }, + { + value: 'x-any-recipient' + name: 'Any Recipient' + }, + { + value: '' + name: '-' + disable: true + }, + { + value: '' + name: '- ' + App.i18n.translateInline('expert settings') + ' -' + disable: true + }, + { + value: '' + name: '-' + disable: true + }, + { + value: 'x-spam-flag' + name: 'X-Spam-Flag' + }, + { + value: 'x-spam-level' + name: 'X-Spam-Level' + }, + { + value: 'x-spam-score' + name: 'X-Spam-Score' + }, + { + value: 'x-spam-status' + name: 'X-Spam-Status' + }, + { + value: 'importance' + name: 'Importance' + }, + { + value: 'x-priority' + name: 'X-Priority' + }, + + { + value: 'organization' + name: 'Organization' + }, + + { + value: 'x-original-to' + name: 'X-Original-To' + }, + { + value: 'delivered-to' + name: 'Delivered-To' + }, + { + value: 'envelope-to' + name: 'Envelope-To' + }, + { + value: 'return-path' + name: 'Return-Path' + }, + { + value: 'mailing-list' + name: 'Mailing-List' + }, + { + value: 'list-id' + name: 'List-Id' + }, + { + value: 'list-archive' + name: 'List-Archive' + }, + { + value: 'mailing-list' + name: 'Mailing-List' + }, + { + value: 'auto-submitted' + name: 'Auto-Submitted' + }, + { + value: 'x-loop' + name: 'X-Loop' + }, + ] + for listItem in loopData + listItem.value = "#{ attribute.name }::#{listItem.value}" + add = { name: '', display: '', tag: 'select', multiple: false, null: false, nulloption: true, options: loopData, translate: true, required: false } + item.find('.addSelection').append( form_controller.formGenItem( add ) ) + + # bind add click + item.find('.add').bind('click', (e) -> + e.preventDefault() + name = $(@).closest('.controls').find('.addSelection').find('select').val() + displayName = $(@).closest('.controls').find('.addSelection').find('select option:selected').html() + return if !name + addItem( name, displayName, $(@) ) + ) + + # show default values + loopDataValue = {} + if attribute.value + for key, value of attribute.value + displayName = key + for listItem in loopData + if listItem.value is "#{ attribute.name }::#{key}" + addItem( "#{ attribute.name }::#{key}", listItem.name, item.find('.add'), value ) + + item diff --git a/app/assets/javascripts/app/controllers/_ui_element/postmaster_set.js.coffee b/app/assets/javascripts/app/controllers/_ui_element/postmaster_set.js.coffee new file mode 100644 index 000000000..9c4170950 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_ui_element/postmaster_set.js.coffee @@ -0,0 +1,124 @@ +class App.UiElement.postmaster_set + @render: (attribute, params, form_controller) -> + addItem = (key, displayName, el, defaultValue = '') => + collection = undefined + for listItem in loopData + if listItem.value is key + collection = listItem + if collection.relation + add = { name: key, display: displayName, tag: 'select', multiple: false, null: false, nulloption: true, relation: collection.relation, translate: true, default: defaultValue } + else if collection.options + add = { name: key, display: displayName, tag: 'select', multiple: false, null: false, nulloption: true, options: collection.options, translate: true, default: defaultValue } + else + add = { name: key, display: displayName, tag: 'input', null: false, default: defaultValue } + itemInput = $( form_controller.formGenItem( add ).append('' ) ) + + # remove on click + itemInput.find('.remove').bind('click', (e) -> + e.preventDefault() + key = $(e.target).closest('.form-group').find('select, input').attr('name') + return if !key + $(e.target).closest('.controls').find('.addSelection select option[value="' + key + '"]').show() + $(e.target).closest('.controls').find('.addSelection select option[value="' + key + '"]').prop('disabled', false) + $(e.target).closest('.form-group').remove() + ) + + # add new item + control = el.closest('.perform_set') + control.find('.list').append(itemInput) + control.find('.addSelection select').val('') + control.find('.addSelection select option[value="' + key + '"]').prop('disabled', true) + control.find('.addSelection select option[value="' + key + '"]').hide() + + # scaffold of perform elements + item = $(' +
+
+
+
+
+
+ +
+
') + + + # select shown attributes + loopData = [ + { + value: 'x-zammad-ticket-priority_id' + name: 'Ticket Priority' + relation: 'TicketPriority' + }, + { + value: 'x-zammad-ticket-state_id' + name: 'Ticket State' + relation: 'TicketState' + }, + { + value: 'x-zammad-ticket-customer' + name: 'Ticket Customer' + }, + { + value: 'x-zammad-ticket-group_id' + name: 'Ticket Group' + relation: 'Group' + }, + { + value: 'x-zammad-ticket-owner' + name: 'Ticket Owner' + }, + { + value: '' + name: '-' + disable: true + }, + { + value: 'x-zammad-article-internal' + name: 'Article Internal' + options: { true: 'Yes', false: 'No'} + }, + { + value: 'x-zammad-article-type_id' + name: 'Article Type' + relation: 'TicketArticleType' + }, + { + value: 'x-zammad-article-sender_id' + name: 'Article Sender' + relation: 'TicketArticleSender' + }, + { + value: '' + name: '-' + disable: true + }, + { + value: 'x-zammad-ignore' + name: 'Ignore Message' + options: { true: 'Yes', false: 'No'} + }, + ] + for listItem in loopData + listItem.value = "#{ attribute.name }::#{listItem.value}" + add = { name: '', display: '', tag: 'select', multiple: false, null: false, nulloption: true, options: loopData, translate: true, required: false } + item.find('.addSelection').append( form_controller.formGenItem( add ) ) + + item.find('.add').bind('click', (e) -> + e.preventDefault() + name = $(@).closest('.controls').find('.addSelection').find('select').val() + displayName = $(@).closest('.controls').find('.addSelection').find('select option:selected').html() + return if !name + addItem( name, displayName, $(@) ) + ) + + # show default values + loopDataValue = {} + if attribute.value + for key, value of attribute.value + displayName = key + for listItem in loopData + if listItem.value is "#{ attribute.name }::#{key}" + addItem( "#{ attribute.name }::#{key}", listItem.name, item.find('.add'), value ) + + item \ No newline at end of file diff --git a/app/assets/javascripts/app/controllers/_ui_element/radio.js.coffee b/app/assets/javascripts/app/controllers/_ui_element/radio.js.coffee new file mode 100644 index 000000000..8f94a4b49 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_ui_element/radio.js.coffee @@ -0,0 +1,25 @@ +class App.UiElement.radio extends App.UiElement.ApplicationUiElement + @render: (attribute, params) -> + + # build options list based on config + @getConfigOptionList( attribute, params ) + + # build options list based on relation + @getRelationOptionList( attribute, params ) + + # add null selection if needed + @addNullOption( attribute, params ) + + # sort attribute.options + @sortOptions( attribute, params ) + + # finde selected/checked item of list + @selectedOptions( attribute, params ) + + # disable item of list + @disabledOptions( attribute, params ) + + # filter attributes + @filterOption( attribute, params ) + + $( App.view('generic/radio')( attribute: attribute ) ) diff --git a/app/assets/javascripts/app/controllers/_ui_element/richtext.js.coffee b/app/assets/javascripts/app/controllers/_ui_element/richtext.js.coffee new file mode 100644 index 000000000..058c4a1cb --- /dev/null +++ b/app/assets/javascripts/app/controllers/_ui_element/richtext.js.coffee @@ -0,0 +1,100 @@ +class App.UiElement.richtext + @render: (attribute) -> + + item = $( App.view('generic/richtext')( attribute: attribute ) ) + item.find('[contenteditable]').ce( + mode: attribute.type + maxlength: attribute.maxlength + ) + if attribute.upload + item.append( $( App.view('generic/attachment')( attribute: attribute ) ) ) + + renderAttachment = (file) => + item.find('.attachments').append( App.view('generic/attachment_item')( + fileName: file.filename + fileSize: @humanFileSize( file.size ) + store_id: file.store_id + )) + item.on( + 'click' + "[data-id=#{file.store_id}]", (e) => + @attachments = _.filter( + @attachments, + (item) -> + return if item.id isnt file.store_id + item + ) + store_id = $(e.currentTarget).data('id') + + # delete attachment from storage + App.Ajax.request( + type: 'DELETE' + url: App.Config.get('api_path') + '/ticket_attachment_upload' + data: JSON.stringify( { store_id: store_id } ), + processData: false + success: (data, status, xhr) => + ) + + # remove attachment from dom + element = $(e.currentTarget).closest('.attachments') + $(e.currentTarget).closest('.attachment').remove() + # empty .attachment (remove spaces) to keep css working, thanks @mrflix :-o + if element.find('.attachment').length == 0 + element.empty() + ) + + @attachments = [] + @progressBar = item.find('.attachmentUpload-progressBar') + @progressText = item.find('.js-percentage') + @attachmentPlaceholder = item.find('.attachmentPlaceholder') + @attachmentUpload = item.find('.attachmentUpload') + @attachmentsHolder = item.find('.attachments') + @cancelContainer = item.find('.js-cancel') + + u = => html5Upload.initialize( + uploadUrl: App.Config.get('api_path') + '/ticket_attachment_upload', + dropContainer: item.closest('form').get(0), + cancelContainer: @cancelContainer, + inputField: item.find( 'input' ).get(0), + key: 'File', + data: { form_id: @form_id }, + maxSimultaneousUploads: 1, + onFileAdded: (file) => + + file.on( + + onStart: => + @attachmentPlaceholder.addClass('hide') + @attachmentUpload.removeClass('hide') + @cancelContainer.removeClass('hide') + console.log('upload start') + + onAborted: => + @attachmentPlaceholder.removeClass('hide') + @attachmentUpload.addClass('hide') + + # Called after received response from the server + onCompleted: (response) => + + response = JSON.parse(response) + @attachments.push response.data + + @attachmentPlaceholder.removeClass('hide') + @attachmentUpload.addClass('hide') + + renderAttachment(response.data) + console.log('upload complete', response.data ) + + # Called during upload progress, first parameter + # is decimal value from 0 to 100. + onProgress: (progress, fileSize, uploadedBytes) => + @progressBar.width(parseInt(progress) + "%") + @progressText.text(parseInt(progress)) + # hide cancel on 90% + if parseInt(progress) >= 90 + @cancelContainer.addClass('hide') + console.log('uploadProgress ', parseInt(progress)) + ) + ) + App.Delay.set( u, 100, undefined, 'form_upload' ) + item \ No newline at end of file diff --git a/app/assets/javascripts/app/controllers/_ui_element/searchable_select.js.coffee b/app/assets/javascripts/app/controllers/_ui_element/searchable_select.js.coffee new file mode 100644 index 000000000..bc44fbd8c --- /dev/null +++ b/app/assets/javascripts/app/controllers/_ui_element/searchable_select.js.coffee @@ -0,0 +1,3 @@ +class App.UiElement.searchable_select + @render: (attribute) -> + new App.SearchableSelect( attribute: attribute ).element() diff --git a/app/assets/javascripts/app/controllers/_ui_element/select.js.coffee b/app/assets/javascripts/app/controllers/_ui_element/select.js.coffee new file mode 100644 index 000000000..bf1f79976 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_ui_element/select.js.coffee @@ -0,0 +1,32 @@ +class App.UiElement.select extends App.UiElement.ApplicationUiElement + @render: (attribute, params, form_controller) -> + + # set multible option + if attribute.multiple + attribute.multiple = 'multiple' + else + attribute.multiple = '' + + # build options list based on config + @getConfigOptionList( attribute, params ) + + # build options list based on relation + @getRelationOptionList( attribute, params ) + + # add null selection if needed + @addNullOption( attribute, params ) + + # sort attribute.options + @sortOptions( attribute, params ) + + # finde selected/checked item of list + @selectedOptions( attribute, params ) + + # disable item of list + @disabledOptions( attribute, params ) + + # filter attributes + @filterOption( attribute, params ) + + # return item + $( App.view('generic/select')( attribute: attribute ) ) \ No newline at end of file diff --git a/app/assets/javascripts/app/controllers/_ui_element/sla_times.js.coffee b/app/assets/javascripts/app/controllers/_ui_element/sla_times.js.coffee new file mode 100644 index 000000000..76b18f04d --- /dev/null +++ b/app/assets/javascripts/app/controllers/_ui_element/sla_times.js.coffee @@ -0,0 +1,4 @@ +class App.UiElement.sla_times + @render: (attribute) -> + + $( App.view('generic/sla_times')( attribute: attribute ) ) diff --git a/app/assets/javascripts/app/controllers/_ui_element/tag.js.coffee b/app/assets/javascripts/app/controllers/_ui_element/tag.js.coffee new file mode 100644 index 000000000..19c66ccc6 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_ui_element/tag.js.coffee @@ -0,0 +1,8 @@ +class App.UiElement.tag + @render: (attribute) -> + item = $( App.view('generic/input')( attribute: attribute ) ) + a = => + $('#' + attribute.id ).tokenfield() + $('#' + attribute.id ).parent().css('height', 'auto') + App.Delay.set( a, 120, undefined, 'tags' ) + item \ No newline at end of file diff --git a/app/assets/javascripts/app/controllers/_ui_element/textarea.js.coffee b/app/assets/javascripts/app/controllers/_ui_element/textarea.js.coffee new file mode 100644 index 000000000..e993837f8 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_ui_element/textarea.js.coffee @@ -0,0 +1,41 @@ +class App.UiElement.textarea + @render: (attribute) -> + fileUploaderId = 'file-uploader-' + new Date().getTime() + '-' + Math.floor( Math.random() * 99999 ) + item = $( App.view('generic/textarea')( attribute: attribute ) + '
' ) + + a = => + visible = $( item[0] ).is(":visible") + if visible && !$( item[0] ).expanding('active') + $( item[0] ).expanding() + $( item[0] ).on('focus', -> + visible = $( item[0] ).is(":visible") + if visible && !$( item[0] ).expanding('active') + $( item[0] ).expanding().focus() + ) + App.Delay.set( a, 80 ) + + if attribute.upload + + # add file uploader + u = => + # only add upload item if html element exists + if @el.find('#' + fileUploaderId )[0] + @el.find('#' + fileUploaderId ).fineUploader( + request: + endpoint: App.Config.get('api_path') + '/ticket_attachment_upload' + params: + form_id: @form_id + text: + uploadButton: '' + template: '
' + + '
{dragZoneText}
' + + '
{uploadButtonText}
' + + '' + + '
', + classes: + success: '' + fail: '' + debug: false + ) + App.Delay.set( u, 100, undefined, 'form_upload' ) + item \ No newline at end of file diff --git a/app/assets/javascripts/app/controllers/_ui_element/timezone.js.coffee b/app/assets/javascripts/app/controllers/_ui_element/timezone.js.coffee new file mode 100644 index 000000000..d3a22f303 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_ui_element/timezone.js.coffee @@ -0,0 +1,25 @@ +class App.UiElement.timezone extends App.UiElement.ApplicationUiElement + @render: (attribute) -> + + attribute.options = [] + timezones = App.Config.get('timezones') + + # build list based on config + for timezone_value, timezone_diff of timezones + if timezone_diff > 0 + timezone_diff = '+' + timezone_diff + item = + name: "#{timezone_value} (GMT#{timezone_diff})" + value: timezone_value + attribute.options.push item + + # add null selection if needed + @addNullOption( attribute, params ) + + # sort attribute.options + @sortOptions( attribute, params ) + + # finde selected/checked item of list + @selectedOptions( attribute, params ) + + $( App.view('generic/select')( attribute: attribute ) ) diff --git a/app/assets/javascripts/app/controllers/_ui_element/user_autocompletion.js.coffee b/app/assets/javascripts/app/controllers/_ui_element/user_autocompletion.js.coffee new file mode 100644 index 000000000..e5e7c3495 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_ui_element/user_autocompletion.js.coffee @@ -0,0 +1,3 @@ +class App.UiElement.user_autocompletion + @render: (attribute) -> + new App.UserOrganizationAutocompletion( attribute: attribute ).element() diff --git a/app/assets/javascripts/app/index.js.coffee b/app/assets/javascripts/app/index.js.coffee index 903beea27..11f9c2732 100644 --- a/app/assets/javascripts/app/index.js.coffee +++ b/app/assets/javascripts/app/index.js.coffee @@ -254,4 +254,6 @@ class App extends Spine.Controller JST["app/views/#{name}"](params) template +class App.UiElement + window.App = App