Moved to new table api.

This commit is contained in:
Martin Edenhofer 2014-06-14 11:26:08 +02:00
parent 7a0e95014c
commit e417001822
19 changed files with 687 additions and 555 deletions

View file

@ -71,7 +71,7 @@ class App.ControllerGenericEdit extends App.ControllerModal
submit: (e) -> submit: (e) ->
e.preventDefault() e.preventDefault()
params = @formParam(e.target) params = @formParam(e.target)
@item.load(params) @item.load(params)
# validate # validate
@ -169,20 +169,14 @@ class App.ControllerGenericIndex extends App.Controller
objects: objects objects: objects
overview: overview overview: overview
attributes: attributes attributes: attributes
groupBy: 'state' bindRow:
events:
'click': @edit
) )
binds = {} edit: (id, e) =>
for item in attributes
if item.dataType
if !binds[item.dataType]
callback = item.callback || @edit
@el.on( 'click', "[data-type=#{item.dataType}]", callback )
binds[item.dataType] = true
edit: (e) =>
e.preventDefault() e.preventDefault()
item = $(e.target).item( App[ @genericObject ] ) item = App[ @genericObject ].find(id)
if @editCallback if @editCallback
@editCallback(item) @editCallback(item)

View file

@ -4,48 +4,64 @@ class App.ControllerTable extends App.Controller
@[key] = value @[key] = value
@table = @tableGen(params) @table = @tableGen(params)
if @el if @el
@el.append( @table ) @el.append( @table )
### ###
# table simple based on model
rowClick = -> (id, e)
e.preventDefault()
console.log('rowClick', id)
rowMouseover = -> (id, e)
e.preventDefault()
console.log('rowMouseover', id)
rowMouseout = -> (id, e)
e.preventDefault()
console.log('rowMouseout', id)
rowDblClick = -> (id, e)
e.preventDefault()
console.log('rowDblClick', id)
colClick = -> (id, e)
e.preventDefault()
console.log('colClick', e.target)
checkboxClick = -> (id, e)
e.preventDefault()
console.log('checkboxClick', e.target)
new App.ControllerTable( new App.ControllerTable(
header: ['Host', 'User', 'Adapter', 'Active']
overview: ['host', 'user', 'adapter', 'active'] overview: ['host', 'user', 'adapter', 'active']
model: App.Channel model: App.Channel
objects: data objects: data
groupBy: 'group'
checkbox: false checkbox: false
radio: false radio: false
bindRow:
events:
'click': rowClick
'mouseover': rowMouseover
'mouseout': rowMouseout
'dblclick': rowDblClick
bindCol:
host:
events:
'click': colEvent
bindCheckbox:
events:
'click': rowClick
'mouseover': rowMouseover
'mouseout': rowMouseout
'dblclick': rowDblClick
) )
new App.ControllerTable(
overview_extended: [
{ name: 'number', link: true }
{ name: 'title', link: true }
{ name: 'customer', class: 'user-popover', data: { id: true } }
{ name: 'state', translate: true }
{ name: 'priority', translate: true }
{ name: 'group' },
{ name: 'owner', class: 'user-popover', data: { id: true } }
{ name: 'created_at', callback: @frontendTime }
{ name: 'last_contact', callback: @frontendTime }
{ name: 'last_contact_agent', callback: @frontendTime }
{ name: 'last_contact_customer', callback: @frontendTime }
{ name: 'first_response', callback: @frontendTime }
{ name: 'close_time', callback: @frontendTime }
],
model: App.Ticket
objects: tickets
checkbox: false
radio: false
)
### ###
tableGen: (data) -> tableGen: (data) ->
overview = data.overview || data.model.configure_overview || [] overview = data.overview || data.model.configure_overview || []
attributes = data.attributes || data.model.configure_attributes || {} attributes = data.attributes || data.model.configure_attributes || {}
header = data.header
destroy = data.model.configure_delete destroy = data.model.configure_delete
# check if table is empty # check if table is empty
@ -53,108 +69,132 @@ class App.ControllerTable extends App.Controller
table = '<p>-' + App.i18n.translateContent( 'none' ) + '-</p>' table = '<p>-' + App.i18n.translateContent( 'none' ) + '-</p>'
return $(table) return $(table)
# define table header # group by
if header if data.groupBy
header_new = []
for key in header
header_new.push {
display: key
}
header = header_new
else if !data.overview_extended
header = []
for row in overview
found = false
if attributes
for attribute in attributes
if row is attribute.name
found = true
header.push attribute
else
rowWithoutId = row + '_id'
if rowWithoutId is attribute.name
found = true
header.push attribute
if !found
header.push {
name: row
display: row
}
# collect data of col. types # remove group by attribute from header
dataTypesForCols = [] overview = _.filter(
for row in overview overview
(item) =>
return item if item isnt data.groupBy
return
)
if !_.isEmpty(attributes) # get new order
for attribute in attributes groupObjects = _.groupBy(
found = false data.objects
if row is attribute.name (item) =>
found = true return '' if !item[data.groupBy]
dataTypesAttribute = _.clone(attribute) return item[data.groupBy].displayName() if item[data.groupBy].displayName
else if row + '_id' is attribute.name item[data.groupBy]
found = true )
dataTypesAttribute = _.clone(attribute) groupOrder = []
dataTypesAttribute['name'] = row for group, value of groupObjects
if found groupOrder.push group
dataTypesAttribute['type'] = 'link'
if !dataTypesAttribute['dataType']
dataTypesAttribute['dataType'] = 'edit'
dataTypesForCols.push dataTypesAttribute
else
dataTypesForCols.push {
name: row
type: 'link'
dataType: 'edit'
}
# extended table format # sort new groups
if data.overview_extended groupOrder = _.sortBy(
if !header groupOrder
header = [] (item) =>
for row in data.overview_extended item
for attribute in attributes )
if row.name is attribute.name
header.push attribute
else
rowWithoutId = row.name + '_id'
if rowWithoutId is attribute.name
header.push attribute
dataTypesForCols = data.overview_extended # create new data array
data.objects = []
for group in groupOrder
data.objects = data.objects.concat groupObjects[group]
groupObjects[group] = [] # release old array
# generate content data # get header data
for object in data.objects header = []
for item in overview
headerFound = false
for attribute in attributes
if attribute.name is item
headerFound = true
header.push attribute
else
rowWithoutId = item + '_id'
if attribute.name is rowWithoutId
headerFound = true
header.push attribute
# check if info for each col. is already there # get content
for row in dataTypesForCols @log 'debug', 'table', 'header', header, 'overview', 'objects', data.objects
# lookup relation
if !object[row.name]
rowWithoutId = row.name + '_id'
for attribute in attributes
if rowWithoutId is attribute.name
if attribute.relation && App[ attribute.relation ]
if App[ attribute.relation ].exists( object[rowWithoutId] )
record = App[ attribute.relation ].find( object[rowWithoutId] )
object[row.name] = record.name
@log 'debug', 'table', 'header', header, 'overview', dataTypesForCols, 'objects', data.objects
table = App.view('generic/table')( table = App.view('generic/table')(
header: header header: header
overview: dataTypesForCols
objects: data.objects objects: data.objects
checkbox: data.checkbox checkbox: data.checkbox
radio: data.radio radio: data.radio
groupBy: data.groupBy groupBy: data.groupBy
destroy: destroy destroy: destroy
callbacks: data.callbackAttributes
) )
# convert to jquery object # convert to jquery object
table = $(table) table = $(table)
cursorMap =
click: 'pointer'
dblclick: 'pointer'
#mouseover: 'alias'
# bind col.
if data.bindCol
for name, item of data.bindCol
if item.events
position = 0
if data.checkbox
position += 1
hit = false
for headerName in header
if !hit
position += 1
if headerName.name is name || headerName.name is "#{name}_id"
hit = true
if hit
for event, callback of item.events
do (table, event, callback) =>
if cursorMap[event]
table.find("tbody > tr > td:nth-child(#{position}) > span").css( 'cursor', cursorMap[event] )
table.on( event, "tbody > tr > td:nth-child(#{position}) > span",
(e) =>
e.stopPropagation()
id = $(e.target).parents('tr').data('id')
callback(id, e)
)
# bind row
if data.bindRow
if data.bindRow.events
for event, callback of data.bindRow.events
do (table, event, callback) =>
if cursorMap[event]
table.find('tbody > tr').css( 'cursor', cursorMap[event] )
table.on( event, 'tbody > tr',
(e) =>
id = $(e.target).parents('tr').data('id')
callback(id, e)
)
# bind bindCheckbox
if data.bindCheckbox
if data.bindCheckbox.events
for event, callback of data.bindCheckbox.events
do (table, event, callback) =>
table.delegate('input[name="bulk"]', event, (e) ->
e.stopPropagation()
id = $(e.target).parents('tr').data('id')
checked = $(e.target).prop('checked')
callback(id, checked, e)
)
# bind on delete dialog # bind on delete dialog
if data.model && destroy if data.model && destroy
table.delegate('[data-type="destroy"]', 'click', (e) -> table.delegate('[data-type="destroy"]', 'click', (e) ->
e.stopPropagation()
e.preventDefault() e.preventDefault()
itemId = $(e.target).parents('tr').data('id') itemId = $(e.target).parents('tr').data('id')
item = data.model.find(itemId) item = data.model.find(itemId)
@ -165,11 +205,24 @@ class App.ControllerTable extends App.Controller
# enable checkbox bulk selection # enable checkbox bulk selection
if data.checkbox if data.checkbox
table.delegate('[name="bulk_all"]', 'click', (e) -> table.delegate('input[name="bulk_all"]', 'click', (e) ->
e.stopPropagation()
if $(e.target).prop('checked') if $(e.target).prop('checked')
$(e.target).parents().find('[name="bulk"]').prop( 'checked', true ); $(e.target).parents('table').find('[name="bulk"]').each( ->
if !$(@).prop('checked')
#$(@).prop('checked', true)
$(@).trigger('click')
)
else else
$(e.target).parents().find('[name="bulk"]').prop( 'checked', false ); $(e.target).parents('table').find('[name="bulk"]').each( ->
if $(@).prop('checked')
#$(@).prop('checked', false)
$(@).trigger('click')
)
) )
return table time = =>
@frontendTimeUpdate()
@delay(time, 80)
table

View file

@ -1,8 +1,3 @@
$.fn.item = (genericObject) ->
elementID = $(@).data('id')
elementID or= $(@).parents('[data-id]').data('id')
genericObject.find(elementID)
class App.ChannelEmail extends App.ControllerTabs class App.ChannelEmail extends App.ControllerTabs
constructor: -> constructor: ->
super super
@ -46,7 +41,6 @@ class App.ChannelEmail extends App.ControllerTabs
class App.ChannelEmailFilter extends App.Controller class App.ChannelEmailFilter extends App.Controller
events: events:
'click [data-type=new]': 'new' 'click [data-type=new]': 'new'
'click [data-type=edit]': 'edit'
constructor: -> constructor: ->
super super
@ -59,9 +53,12 @@ class App.ChannelEmailFilter extends App.Controller
template = $( '<div><div class="overview"></div><a data-type="new" class="btn btn-default">' + App.i18n.translateContent('New') + '</a></div>' ) template = $( '<div><div class="overview"></div><a data-type="new" class="btn btn-default">' + App.i18n.translateContent('New') + '</a></div>' )
new App.ControllerTable( new App.ControllerTable(
el: template.find('.overview'), el: template.find('.overview')
model: App.PostmasterFilter, model: App.PostmasterFilter
objects: data, objects: data
bindRow:
events:
'click': @edit
) )
@html template @html template
@ -69,10 +66,9 @@ class App.ChannelEmailFilter extends App.Controller
e.preventDefault() e.preventDefault()
new App.ChannelEmailFilterEdit( {} ) new App.ChannelEmailFilterEdit( {} )
edit: (e) => edit: (id, e) =>
e.preventDefault() e.preventDefault()
item = $(e.target).item( App.PostmasterFilter ) new App.ChannelEmailFilterEdit( object: App.PostmasterFilter.find(id) )
new App.ChannelEmailFilterEdit( object: item )
class App.ChannelEmailFilterEdit extends App.ControllerModal class App.ChannelEmailFilterEdit extends App.ControllerModal
constructor: -> constructor: ->
@ -135,7 +131,6 @@ class App.ChannelEmailFilterEdit extends App.ControllerModal
class App.ChannelEmailAddress extends App.Controller class App.ChannelEmailAddress extends App.Controller
events: events:
'click [data-type=new]': 'new' 'click [data-type=new]': 'new'
'click [data-type=edit]': 'edit'
constructor: -> constructor: ->
super super
@ -151,6 +146,9 @@ class App.ChannelEmailAddress extends App.Controller
el: template.find('.overview') el: template.find('.overview')
model: App.EmailAddress model: App.EmailAddress
objects: data objects: data
bindRow:
events:
'click': @edit
) )
@html template @html template
@ -159,9 +157,9 @@ class App.ChannelEmailAddress extends App.Controller
e.preventDefault() e.preventDefault()
new App.ChannelEmailAddressEdit( {} ) new App.ChannelEmailAddressEdit( {} )
edit: (e) => edit: (id, e) =>
e.preventDefault() e.preventDefault()
item = $(e.target).item( App.EmailAddress ) item = App.EmailAddress.find(id)
new App.ChannelEmailAddressEdit( object: item ) new App.ChannelEmailAddressEdit( object: item )
class App.ChannelEmailAddressEdit extends App.ControllerModal class App.ChannelEmailAddressEdit extends App.ControllerModal
@ -223,7 +221,6 @@ class App.ChannelEmailAddressEdit extends App.ControllerModal
class App.ChannelEmailSignature extends App.Controller class App.ChannelEmailSignature extends App.Controller
events: events:
'click [data-type=new]': 'new' 'click [data-type=new]': 'new'
'click [data-type=edit]': 'edit'
constructor: -> constructor: ->
super super
@ -238,17 +235,19 @@ class App.ChannelEmailSignature extends App.Controller
el: template.find('.overview') el: template.find('.overview')
model: App.Signature model: App.Signature
objects: data objects: data
bindRow:
events:
'click': @edit
) )
@html template @html template
new: (e) => new: (e) =>
e.preventDefault() e.preventDefault()
new App.ChannelEmailSignatureEdit( {} ) new App.ChannelEmailSignatureEdit( {} )
edit: (e) => edit: (id, e) =>
e.preventDefault() e.preventDefault()
item = $(e.target).item( App.Signature ) item = App.Signature.find(id)
new App.ChannelEmailSignatureEdit( object: item ) new App.ChannelEmailSignatureEdit( object: item )
class App.ChannelEmailSignatureEdit extends App.ControllerModal class App.ChannelEmailSignatureEdit extends App.ControllerModal
@ -310,31 +309,23 @@ class App.ChannelEmailSignatureEdit extends App.ControllerModal
class App.ChannelEmailInbound extends App.Controller class App.ChannelEmailInbound extends App.Controller
events: events:
'click [data-type=new]': 'new' 'click [data-type=new]': 'new'
'click [data-type=edit]': 'edit'
constructor: -> constructor: ->
super super
App.Channel.subscribe( @render, initFetch: true ) App.Channel.subscribe( @render, initFetch: true )
render: => render: =>
channels = App.Channel.all() channels = App.Channel.search( filter: { area: 'Email::Inbound' } )
data = []
for channel in channels
if channel.area is 'Email::Inbound'
channel.host = channel.options['host']
channel.user = channel.options['user']
data.push channel
template = $( '<div><div class="overview"></div><a data-type="new" class="btn btn-default">' + App.i18n.translateContent('New') + '</a></div>' ) template = $( '<div><div class="overview"></div><a data-type="new" class="btn btn-default">' + App.i18n.translateContent('New') + '</a></div>' )
new App.ControllerTable( new App.ControllerTable(
el: template.find('.overview'), el: template.find('.overview')
header: ['Host', 'User', 'Adapter', 'Active'], model: App.Channel
overview: ['host', 'user', 'adapter', 'active'], objects: channels
model: App.Channel, bindRow:
objects: data, events:
'click': @edit
) )
@html template @html template
@ -342,9 +333,9 @@ class App.ChannelEmailInbound extends App.Controller
e.preventDefault() e.preventDefault()
new App.ChannelEmailInboundEdit( {} ) new App.ChannelEmailInboundEdit( {} )
edit: (e) => edit: (id, e) =>
e.preventDefault() e.preventDefault()
item = $(e.target).item( App.Channel ) item = App.Channel.find(id)
new App.ChannelEmailInboundEdit( object: item ) new App.ChannelEmailInboundEdit( object: item )
@ -354,29 +345,13 @@ class App.ChannelEmailInboundEdit extends App.ControllerModal
@render(@object) @render(@object)
render: (data = {}) -> render: (data = {}) ->
if !data['options']
data['options'] = {}
data['options']['ssl'] = true
data['active'] = true
configure_attributes = [
{ name: 'adapter', display: 'Type', tag: 'select', multiple: false, null: false, options: { IMAP: 'IMAP', POP3: 'POP3' } , class: 'span4', default: data['adapter'] },
{ name: 'host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, class: 'span4', autocapitalize: false, default: (data['options']&&data['options']['host']) },
{ name: 'user', display: 'User', tag: 'input', type: 'text', limit: 120, null: false, class: 'span4', autocapitalize: false, default: (data['options']&&data['options']['user']) },
{ name: 'password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: false, class: 'span4', autocapitalize: false, default: (data['options']&&data['options']['password']) },
{ name: 'ssl', display: 'SSL', tag: 'select', multiple: false, null: false, options: { true: 'yes', false: 'no' }, translate: true, class: 'span4', default: (data['options']&&data['options']['ssl']) },
{ name: 'folder', display: 'Folder', tag: 'input', type: 'text', limit: 120, null: true, class: 'span4', autocapitalize: false, default: (data['options']&&data['options']['folder']) },
{ name: 'group_id', display: 'Group', tag: 'select', multiple: false, null: false, filter: @edit_form, nulloption: false, relation: 'Group', class: 'span4', default: data['group_id'] },
{ name: 'active', display: 'Active', tag: 'select', multiple: false, null: false, options: { true: 'yes', false: 'no' } , translate: true, class: 'span4', default: data['active'] },
]
if @object if @object
@html App.view('generic/admin/edit')( @html App.view('generic/admin/edit')(
head: 'Email Channel' head: 'Email Channel'
) )
@form = new App.ControllerForm( @form = new App.ControllerForm(
el: @el.find('#object_edit') el: @el.find('#object_edit')
model: { configure_attributes: configure_attributes, className: '' } model: App.Channel
autofocus: true autofocus: true
) )
else else
@ -385,7 +360,7 @@ class App.ChannelEmailInboundEdit extends App.ControllerModal
) )
@form = new App.ControllerForm( @form = new App.ControllerForm(
el: @el.find('#object_new') el: @el.find('#object_new')
model: { configure_attributes: configure_attributes, className: '' } model: App.Channel
autofocus: true autofocus: true
) )
@modalShow() @modalShow()
@ -395,21 +370,10 @@ class App.ChannelEmailInboundEdit extends App.ControllerModal
# get params # get params
params = @formParam(e.target) params = @formParam(e.target)
params['area'] = 'Email::Inbound'
object = @object || new App.Channel object = @object || new App.Channel
object.load( object.load(params)
area: 'Email::Inbound'
adapter: params['adapter']
group_id: params['group_id']
options: {
host: params['host']
user: params['user']
password: params['password']
ssl: params['ssl']
folder: params['folder']
},
active: params['active']
)
# validate form # validate form
errors = @form.validate( params ) errors = @form.validate( params )
@ -463,7 +427,7 @@ class App.ChannelEmailOutbound extends App.Controller
channel_used = channel channel_used = channel
configure_attributes = [ configure_attributes = [
{ name: 'adapter', display: 'Send Mails via', tag: 'select', multiple: false, null: false, options: adapters , class: 'span4', default: adapter_used }, { name: 'adapter', display: 'Send Mails via', tag: 'select', multiple: false, null: false, options: adapters , default: adapter_used },
] ]
new App.ControllerForm( new App.ControllerForm(
el: @el.find('#form-email-adapter'), el: @el.find('#form-email-adapter'),
@ -476,10 +440,10 @@ class App.ChannelEmailOutbound extends App.Controller
if adapter_used is 'SMTP' if adapter_used is 'SMTP'
configure_attributes = [ configure_attributes = [
{ name: 'host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, class: 'span4', autocapitalize: false, default: (channel_used['options']&&channel_used['options']['host']) }, { name: 'host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, default: (channel_used['options']&&channel_used['options']['host']) },
{ name: 'user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, class: 'span4', autocapitalize: false, default: (channel_used['options']&&channel_used['options']['user']) }, { name: 'user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false, default: (channel_used['options']&&channel_used['options']['user']) },
{ name: 'password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, class: 'span4', autocapitalize: false, default: (channel_used['options']&&channel_used['options']['password']) }, { name: 'password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false, default: (channel_used['options']&&channel_used['options']['password']) },
{ name: 'ssl', display: 'SSL', tag: 'select', multiple: false, null: false, options: { true: 'yes', false: 'no' } , class: 'span4', translate: true, default: (channel_used['options']&&channel_used['options']['ssl']) }, { name: 'ssl', display: 'SSL', tag: 'select', multiple: false, null: false, options: { true: 'yes', false: 'no' } , translate: true, default: (channel_used['options']&&channel_used['options']['ssl']) },
{ name: 'port', display: 'Port', tag: 'input', type: 'text', limit: 5, null: false, class: 'span1', autocapitalize: false, default: ((channel_used['options']&&channel_used['options']['port']) || 25) }, { name: 'port', display: 'Port', tag: 'input', type: 'text', limit: 5, null: false, class: 'span1', autocapitalize: false, default: ((channel_used['options']&&channel_used['options']['port']) || 25) },
] ]
@form = new App.ControllerForm( @form = new App.ControllerForm(

View file

@ -1,6 +1,5 @@
class App.DashboardTicket extends App.Controller class App.DashboardTicket extends App.Controller
events: events:
'click [data-type=edit]': 'zoom'
'click [data-type=settings]': 'settings' 'click [data-type=settings]': 'settings'
'click [data-type=page]': 'page' 'click [data-type=page]': 'page'
@ -18,11 +17,11 @@ class App.DashboardTicket extends App.Controller
# render # render
@fetch() @fetch()
fetch: => fetch: (force) =>
# use cache of first page # use cache of first page
cache = App.Store.get( @key ) cache = App.Store.get( @key )
if cache if !force && cache
@load( cache ) @load( cache )
# init fetch via ajax, all other updates on time via websockets # init fetch via ajax, all other updates on time via websockets
@ -55,12 +54,13 @@ class App.DashboardTicket extends App.Controller
App.Overview.unbind('local:rerender') App.Overview.unbind('local:rerender')
App.Overview.bind 'local:rerender', (record) => App.Overview.bind 'local:rerender', (record) =>
@log 'notice', 'rerender...', record @log 'notice', 'rerender...', record
data.overview = record
@render(data) @render(data)
App.Overview.unbind('local:refetch') App.Overview.unbind('local:refetch')
App.Overview.bind 'local:refetch', (record) => App.Overview.bind 'local:refetch', (record) =>
@log 'notice', 'refetch...', record @log 'notice', 'refetch...', record
@fetch() @fetch(true)
@render( data ) @render( data )
@ -72,8 +72,8 @@ class App.DashboardTicket extends App.Controller
@overview = data.overview @overview = data.overview
@tickets_count = data.tickets_count @tickets_count = data.tickets_count
@ticket_ids = data.ticket_ids @ticket_ids = data.ticket_ids
# FIXME 10 per_page = @overview.view.per_page || 10
pages_total = parseInt( ( @tickets_count / 10 ) + 0.99999 ) || 1 pages_total = parseInt( ( @tickets_count / per_page ) + 0.99999 ) || 1
html = App.view('dashboard/ticket')( html = App.view('dashboard/ticket')(
overview: @overview, overview: @overview,
pages_total: pages_total, pages_total: pages_total,
@ -92,13 +92,39 @@ class App.DashboardTicket extends App.Controller
if @ticket_ids[ i - 1 ] if @ticket_ids[ i - 1 ]
@tickets_in_table.push App.Ticket.retrieve( @ticket_ids[ i - 1 ] ) @tickets_in_table.push App.Ticket.retrieve( @ticket_ids[ i - 1 ] )
shown_all_attributes = @ticketTableAttributes( App.Overview.find(@overview.id).view.d ) openTicket = (id,e) =>
ticket = App.Ticket.retrieve(id)
@navigate ticket.uiUrl()
callbackTicketTitleAdd = (value, object, attribute, attributes, refObject) =>
attribute.title = object.title
callbackLinkToTicket = (value, object, attribute, attributes, refObject) =>
attribute.link = object.uiUrl()
callbackResetLink = (value, object, attribute, attributes, refObject) =>
attribute.link = undefined
callbackUserPopover = (value, object, attribute, attributes, refObject) =>
attribute.class = 'user-popover'
attribute.data =
id: refObject.id
new App.ControllerTable( new App.ControllerTable(
overview: @overview.view.d
el: html.find('.table-overview'), el: html.find('.table-overview'),
overview_extended: shown_all_attributes, model: App.Ticket
model: App.Ticket,
objects: @tickets_in_table, objects: @tickets_in_table,
checkbox: false, checkbox: false
groupBy: @overview.group_by
bindRow:
events:
'click': openTicket
callbackAttributes:
customer_id:
[ callbackResetLink, callbackUserPopover ]
owner_id:
[ callbackResetLink, callbackUserPopover ]
title:
[ callbackLinkToTicket, callbackTicketTitleAdd ]
number:
[ callbackLinkToTicket, callbackTicketTitleAdd ]
) )
@html html @html html
@ -122,8 +148,9 @@ class App.DashboardTicket extends App.Controller
settings: (e) => settings: (e) =>
e.preventDefault() e.preventDefault()
new Settings( new App.OverviewSettings(
overview: App.Overview.find(@overview.id) overview_id: @overview.id
view_mode: 'd'
) )
page: (e) => page: (e) =>
@ -132,139 +159,3 @@ class App.DashboardTicket extends App.Controller
@start_page = id @start_page = id
@fetch() @fetch()
class Settings extends App.ControllerModal
constructor: ->
super
@render()
render: ->
@html App.view('dashboard/ticket_settings')(
overview: @overview,
)
@configure_attributes_article = [
# { name: 'from', display: 'From', tag: 'input', type: 'text', limit: 100, null: false, class: 'span8', },
# { name: 'to', display: 'To', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide' },
# { name: 'type_id', display: 'Type', tag: 'select', multiple: false, null: true, relation: 'TicketArticleType', default: '9', class: 'medium', item_class: 'pull-left' },
# { name: 'internal', display: 'Visibility', tag: 'radio', default: false, null: true, options: { true: 'internal', false: 'public' }, class: 'medium', item_class: 'pull-left' },
{
name: 'per_page',
display: 'Items per page',
tag: 'select',
multiple: false,
null: false,
# default: @overview.view.d.per_page,
options: {
5: 5,
10: 10,
15: 15,
20: 20,
},
class: 'medium',
# item_class: 'pull-left',
},
{
name: 'attributes',
display: 'Attributes',
tag: 'checkbox',
default: @overview.view.d,
null: false,
translate: true
options: {
number: 'Number'
title: 'Title'
customer: 'Customer'
state: 'State'
priority: 'Priority'
group: 'Group'
owner: 'Owner'
created_at: 'Age'
last_contact: 'Last Contact'
last_contact_agent: 'Last Contact Agent'
last_contact_customer: 'Last Contact Customer'
first_response: 'First Response'
close_time: 'Close Time'
escalation_time: 'Escalation in'
article_count: 'Article Count'
},
class: 'medium',
# item_class: 'pull-left',
},
{
name: 'order_by',
display: 'Order',
tag: 'select',
default: @overview.order.by,
null: false,
translate: true
options: {
number: 'Number'
title: 'Title'
customer: 'Customer'
state: 'State'
priority: 'Priority'
group: 'Group'
owner: 'Owner'
created_at: 'Age'
last_contact: 'Last Contact'
last_contact_agent: 'Last Contact Agent'
last_contact_customer: 'Last Contact Customer'
first_response: 'First Response'
close_time: 'Close Time'
escalation_time: 'Escalation in'
article_count: 'Article Count'
},
class: 'medium',
},
{
name: 'order_by_direction',
display: 'Direction',
tag: 'select',
default: @overview.order.direction,
null: false,
translate: true
options: {
ASC: 'up',
DESC: 'down',
},
class: 'medium',
},
]
new App.ControllerForm(
el: @el.find('#form-setting'),
model: { configure_attributes: @configure_attributes_article },
autofocus: false,
)
@modalShow()
submit: (e) =>
e.preventDefault()
params = @formParam(e.target)
# check if refetch is needed
@reload_needed = 0
if @overview.view['d']['per_page'] isnt params['per_page']
@overview.view['d']['per_page'] = params['per_page']
@reload_needed = 1
if @overview.order['by'] isnt params['order_by']
@overview.order['by'] = params['order_by']
@reload_needed = 1
if @overview.order['direction'] isnt params['order_by_direction']
@overview.order['direction'] = params['order_by_direction']
@reload_needed = 1
@overview.view['d'] = params['attributes']
@overview.save(
done: =>
if @reload_needed
@overview.trigger('local:refetch')
else
@overview.trigger('local:rerender')
)
@modalHide()

View file

@ -1,7 +1,6 @@
class Index extends App.Controller class Index extends App.Controller
constructor: -> constructor: ->
super super
@render() @render()
render: -> render: ->
@ -55,11 +54,11 @@ class Table extends App.ControllerContent
# render # render
@fetch() @fetch()
fetch: => fetch: (force) =>
# use cache of first page # use cache of first page
cache = App.Store.get( @key ) cache = App.Store.get( @key )
if cache if !force && cache
@load(cache) @load(cache)
# init fetch via ajax, all other updates on time via websockets # init fetch via ajax, all other updates on time via websockets
@ -106,7 +105,7 @@ class Table extends App.ControllerContent
App.Overview.unbind('local:refetch') App.Overview.unbind('local:refetch')
App.Overview.bind 'local:refetch', (record) => App.Overview.bind 'local:refetch', (record) =>
@log 'notice', 'refetch...', record @log 'notice', 'refetch...', record
@fetch() @fetch(true)
@ticket_list_show = [] @ticket_list_show = []
for ticket_id in @ticket_ids for ticket_id in @ticket_ids
@ -123,6 +122,8 @@ class Table extends App.ControllerContent
render: -> render: ->
# if customer and no ticket exists, show the following message only # if customer and no ticket exists, show the following message only
if !@ticket_list_show[0] && @isRole('Customer') if !@ticket_list_show[0] && @isRole('Customer')
@html App.view('customer_not_ticket_exists')() @html App.view('customer_not_ticket_exists')()
@ -131,6 +132,7 @@ class Table extends App.ControllerContent
@selected = @bulkGetSelected() @selected = @bulkGetSelected()
# set page title # set page title
@overview = App.Overview.find( @overview.id )
@title @overview.name @title @overview.name
# render init page # render init page
@ -182,27 +184,51 @@ class Table extends App.ControllerContent
) )
@el.find('.table-overview').append(table) @el.find('.table-overview').append(table)
else else
shown_all_attributes = @ticketTableAttributes( App.Overview.find( @overview.id ).view.s ) openTicket = (id,e) =>
groupBy = undefined ticket = App.Ticket.retrieve(id)
if @overview.group_by @navigate ticket.uiUrl()
group_by = callbackTicketTitleAdd = (value, object, attribute, attributes, refObject) =>
name: @overview.group_by attribute.title = object.title
id: @overview.group_by + '_id' callbackLinkToTicket = (value, object, attribute, attributes, refObject) =>
attribute.link = object.uiUrl()
callbackResetLink = (value, object, attribute, attributes, refObject) =>
attribute.link = undefined
callbackUserPopover = (value, object, attribute, attributes, refObject) =>
attribute.class = 'user-popover'
attribute.data =
id: refObject.id
callbackCheckbox = (id, checked, e) =>
if @el.find('table').find('input[name="bulk"]:checked').length == 0
@el.find('.bulk-action').addClass('hide')
else
@el.find('.bulk-action').removeClass('hide')
# remove group by attribute from show attributes list
shown_all_attributes = _.filter(
shown_all_attributes
(item) =>
return item if item.name isnt @overview.group_by
return
)
new App.ControllerTable( new App.ControllerTable(
el: @el.find('.table-overview') overview: @overview.view.s
overview_extended: shown_all_attributes el: @el.find('.table-overview')
model: App.Ticket model: App.Ticket
objects: @ticket_list_show objects: @ticket_list_show
checkbox: checkbox checkbox: checkbox
groupBy: group_by groupBy: @overview.group_by
bindRow:
events:
'click': openTicket
#bindCol:
# customer_id:
# events:
# 'mouseover': popOver
callbackAttributes:
customer_id:
[ callbackResetLink, callbackUserPopover ]
owner_id:
[ callbackResetLink, callbackUserPopover ]
title:
[ callbackLinkToTicket, callbackTicketTitleAdd ]
number:
[ callbackLinkToTicket, callbackTicketTitleAdd ]
bindCheckbox:
events:
'click': callbackCheckbox
) )
@bulkSetSelected( @selected ) @bulkSetSelected( @selected )
@ -215,12 +241,13 @@ class Table extends App.ControllerContent
# start bulk action observ # start bulk action observ
@el.find('.bulk-action').append( @bulk_form() ) @el.find('.bulk-action').append( @bulk_form() )
if @el.find('.table-overview').find('[name="bulk"]:checked').length isnt 0 if @el.find('.table-overview').find('input[name="bulk"]:checked').length isnt 0
@el.find('.bulk-action').removeClass('hide') @el.find('.bulk-action').removeClass('hide')
# show/hide bulk action # show/hide bulk action
@el.find('.table-overview').delegate('[name="bulk"], [name="bulk_all"]', 'click', (e) => @el.find('.table-overview').delegate('input[name="bulk"], input[name="bulk_all"]', 'click', (e) =>
if @el.find('.table-overview').find('[name="bulk"]:checked').length == 0 console.log('YES')
if @el.find('.table-overview').find('input[name="bulk"]:checked').length == 0
# hide # hide
@el.find('.bulk-action').addClass('hide') @el.find('.bulk-action').addClass('hide')
@ -324,6 +351,7 @@ class Table extends App.ControllerContent
@fetch() @fetch()
) )
) )
@el.find('.table-overview').find('[name="bulk"]:checked').prop('checked', false)
App.Event.trigger 'notify', { App.Event.trigger 'notify', {
type: 'success' type: 'success'
msg: App.i18n.translateContent('Bulk-Action executed!') msg: App.i18n.translateContent('Bulk-Action executed!')
@ -343,14 +371,15 @@ class Table extends App.ControllerContent
settings: (e) => settings: (e) =>
e.preventDefault() e.preventDefault()
new Settings( new App.OverviewSettings(
overview: App.Overview.find(@overview.id), overview_id: @overview.id
view_mode: @view_mode, view_mode: @view_mode
) )
class Settings extends App.ControllerModal class App.OverviewSettings extends App.ControllerModal
constructor: -> constructor: ->
super super
@overview = App.Overview.find(@overview_id)
@render() @render()
render: -> render: ->
@ -358,119 +387,154 @@ class Settings extends App.ControllerModal
@html App.view('dashboard/ticket_settings')( @html App.view('dashboard/ticket_settings')(
overview: @overview, overview: @overview,
) )
@configure_attributes_article = [ @configure_attributes_article = []
# { name: 'from', display: 'From', tag: 'input', type: 'text', limit: 100, null: false, class: 'span8', }, if @view_mode is 'd'
# { name: 'to', display: 'To', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide' }, @configure_attributes_article.push({
# { name: 'type_id', display: 'Type', tag: 'select', multiple: false, null: true, relation: 'TicketArticleType', default: '9', class: 'medium', item_class: 'pull-left' }, name: 'view::per_page',
# { name: 'internal', display: 'Visibility', tag: 'radio', default: false, null: true, options: { true: 'internal', false: 'public' }, class: 'medium', item_class: 'pull-left' }, display: 'Items per page',
{ tag: 'select',
name: 'per_page' multiple: false,
display: 'Items per page' null: false,
tag: 'select' default: @overview.view.per_page
multiple: false options: {
null: false 5: ' 5'
# default: @overview.view[@view_mode].per_page 10: '10'
options: 15: '15'
15: 15 20: '20'
20: 20 25: '25'
25: 25 },
30: 30 class: 'medium',
35: 35 })
class: 'medium' @configure_attributes_article.push({
# item_class: 'pull-left' name: "view::#{@view_mode}"
}, display: 'Attributes'
{ tag: 'checkbox'
name: 'attributes' default: @overview.view[@view_mode]
display: 'Attributes' null: false
tag: 'checkbox' translate: true
default: @overview.view[@view_mode] options: [
null: false {
translate: true value: 'number'
options: name: 'Number'
# true: 'internal' },
# false: 'public' {
number: 'Number' value: 'title'
title: 'Title' name: 'Title'
customer: 'Customer' },
state: 'State' {
priority: 'Priority' value: 'customer'
group: 'Group' name: 'Customer'
owner: 'Owner' },
created_at: 'Age' {
last_contact: 'Last Contact' value: 'organization'
last_contact_agent: 'Last Contact Agent' name: 'Organization'
last_contact_customer: 'Last Contact Customer' },
first_response: 'First Response' {
close_time: 'Close Time' value: 'state'
escalation_time: 'Escalation in' name: 'State'
article_count: 'Article Count' },
class: 'medium' {
}, value: 'priority'
{ name: 'Priority'
name: 'order_by' },
display: 'Order' {
tag: 'select' value: 'group'
default: @overview.order.by name: 'Group'
null: false },
translate: true {
options: value: 'owner'
number: 'Number' name: 'Owner'
title: 'Title' },
customer: 'Customer' {
state: 'State' value: 'created_at'
priority: 'Priority' name: 'Age'
group: 'Group' },
owner: 'Owner' {
created_at: 'Age' value: 'last_contact'
last_contact: 'Last Contact' name: 'Last Contact'
last_contact_agent: 'Last Contact Agent' },
last_contact_customer: 'Last Contact Customer' {
first_response: 'First Response' value: 'last_contact_agent'
close_time: 'Close Time' name: 'Last Contact Agent'
escalation_time: 'Escalation in' },
article_count: 'Article Count' {
class: 'medium' value: 'last_contact_customer'
}, name: 'Last Contact Customer'
{ },
name: 'order_by_direction' {
display: 'Direction' value: 'first_response'
tag: 'select' name: 'First Response'
default: @overview.order.direction },
null: false {
translate: true value: 'close_time'
options: name: 'Close Time'
ASC: 'up' },
DESC: 'down' {
class: 'medium' value: 'escalation_time'
}, name: 'Escalation in'
{ },
name: 'group_by' {
display: 'Group by' value: 'article_count'
tag: 'select' name: 'Article Count'
default: @overview.group_by },
null: true ]
nulloption: true class: 'medium'
translate: true },
options: {
customer: 'Customer' name: 'order::by'
state: 'State' display: 'Order'
priority: 'Priority' tag: 'select'
group: 'Group' default: @overview.order.by
owner: 'Owner' null: false
class: 'medium' translate: true
}, options:
# { number: 'Number'
# name: 'condition', title: 'Title'
# display: 'Conditions', customer: 'Customer'
# tag: 'select', organization: 'Organization'
# multiple: false, state: 'State'
# null: false, priority: 'Priority'
# relation: 'TicketArticleType', group: 'Group'
# default: '9', owner: 'Owner'
# class: 'medium', created_at: 'Age'
# item_class: 'pull-left', last_contact: 'Last Contact'
# }, last_contact_agent: 'Last Contact Agent'
] last_contact_customer: 'Last Contact Customer'
first_response: 'First Response'
close_time: 'Close Time'
escalation_time: 'Escalation in'
article_count: 'Article Count'
class: 'medium'
},
{
name: 'order::direction'
display: 'Direction'
tag: 'select'
default: @overview.order.direction
null: false
translate: true
options:
ASC: 'up'
DESC: 'down'
class: 'medium'
},
{
name: 'group_by'
display: 'Group by'
tag: 'select'
default: @overview.group_by
null: true
nulloption: true
translate: true
options:
customer: 'Customer'
organization: 'Organization'
state: 'State'
priority: 'Priority'
group: 'Group'
owner: 'Owner'
class: 'medium'
})
new App.ControllerForm( new App.ControllerForm(
el: @el.find('#form-setting') el: @el.find('#form-setting')
@ -486,19 +550,18 @@ class Settings extends App.ControllerModal
# check if refetch is needed # check if refetch is needed
@reload_needed = 0 @reload_needed = 0
if @overview.order['by'] isnt params['order_by'] if @overview.order.by isnt params.order.by
@overview.order['by'] = params['order_by'] @overview.order.by = params.order.by
@reload_needed = 1 @reload_needed = 1
if @overview.order['direction'] isnt params['order_by_direction'] if @overview.order.direction isnt params.order.direction
@overview.order['direction'] = params['order_by_direction'] @overview.order.direction = params.order.direction
@reload_needed = 1 @reload_needed = 1
if @overview['group_by'] isnt params['group_by'] for key, value of params.view
@overview['group_by'] = params['group_by'] @overview.view[key] = value
@reload_needed = 1
@overview.view[@view_mode] = params['attributes'] @overview.group_by = params.group_by
@overview.save( @overview.save(
done: => done: =>

View file

@ -144,7 +144,10 @@ class _i18nSingleton extends Spine.Module
translate: ( string, args... ) => translate: ( string, args... ) =>
# return '' on undefined # return '' on undefined
if typeof string is 'boolean'
string = string.toString()
return '' if string is undefined return '' if string is undefined
return '' if string is ''
# return translation # return translation
if @map[string] isnt undefined if @map[string] isnt undefined

View file

@ -16,6 +16,9 @@ class App.Model extends Spine.Model
uiUrl: -> uiUrl: ->
'#' '#'
translate: ->
App[ @constructor.className ].configure_translate
objectDisplayName: -> objectDisplayName: ->
@constructor.className @constructor.className
@ -67,11 +70,24 @@ class App.Model extends Spine.Model
if !attribute.readonly if !attribute.readonly
# check required // if null is defined && null is false # check required // if null is defined && null is false
if 'null' of attribute && !attribute[null] if 'null' of attribute && !attribute[null]
# key exists not in hash || value is '' || value is undefined # check :: fields
if !( attribute.name of data['params'] ) || data['params'][attribute.name] is '' || data['params'][attribute.name] is undefined parts = attribute.name.split '::'
errors[attribute.name] = 'is required' if parts[0] && !parts[1]
# key exists not in hash || value is '' || value is undefined
if !( attribute.name of data['params'] ) || data['params'][attribute.name] is '' || data['params'][attribute.name] is undefined
errors[attribute.name] = 'is required'
else if parts[0] && parts[1] && !parts[2]
# key exists not in hash || value is '' || value is undefined
if !data.params[parts[0]] || !( parts[1] of data.params[parts[0]] ) || data.params[parts[0]][parts[1]] is '' || data.params[parts[0]][parts[1]] is undefined
errors[attribute.name] = 'is required'
else
throw "can't parse '#{attribute.name}'"
# check confirm password # check confirm password
if attribute.type is 'password' && data['params'][attribute.name] && "#{attribute.name}_confirm" of data['params'] if attribute.type is 'password' && data['params'][attribute.name] && "#{attribute.name}_confirm" of data['params']
@ -82,7 +98,9 @@ class App.Model extends Spine.Model
errors["#{attribute.name}_confirm"] = '' errors["#{attribute.name}_confirm"] = ''
# return error object # return error object
return errors if !_.isEmpty(errors) if !_.isEmpty(errors)
console.log 'error', 'validation vailed', errors
return errors
# return no errors # return no errors
return return
@ -327,5 +345,3 @@ class App.Model extends Spine.Model
return return
) )
collection collection

View file

@ -2,4 +2,25 @@ class App.Channel extends App.Model
@configure 'Channel', 'adapter', 'area', 'options', 'group_id', 'active', 'updated_at' @configure 'Channel', 'adapter', 'area', 'options', 'group_id', 'active', 'updated_at'
@extend Spine.Model.Ajax @extend Spine.Model.Ajax
@url: @apiPath + '/channels' @url: @apiPath + '/channels'
@configure_delete = true @configure_delete = true
@configure_attributes = [
{ name: 'adapter', display: 'Type', tag: 'select', multiple: false, null: false, options: { IMAP: 'IMAP', POP3: 'POP3' } },
{ name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false },
{ name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false },
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: false, autocapitalize: false },
{ name: 'options::ssl', display: 'SSL', tag: 'select', multiple: false, null: false, options: { true: 'yes', false: 'no' }, translate: true, default: true},
{ name: 'options::folder', display: 'Folder', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false },
{ name: 'group_id', display: 'Group', tag: 'select', multiple: false, null: false, nulloption: true, relation: 'Group' },
{ name: 'active', display: 'Active', tag: 'select', multiple: false, null: false, options: { true: 'yes', false: 'no' }, translate: true, default: true },
]
@configure_overview = [
'adapter', 'options::host', 'options::user', 'group'
]
@_fillUp: (data) ->
# group
data.group = App.Group.find( data.group_id )
data

View file

@ -18,21 +18,64 @@ class App.Overview extends App.Model
default: ['number', 'title', 'state', 'created_at'] default: ['number', 'title', 'state', 'created_at']
null: false null: false
translate: true translate: true
options: options: [
number: 'Number' {
title: 'Title' value: 'number'
customer: 'Customer' name: 'Number'
state: 'State' },
priority: 'Priority' {
group: 'Group' value: 'title'
owner: 'Owner' name: 'Title'
created_at: 'Age' },
last_contact: 'Last Contact' {
last_contact_agent: 'Last Contact Agent' value: 'customer'
last_contact_customer: 'Last Contact Customer' name: 'Customer'
first_response: 'First Response' },
close_time: 'Close Time' {
article_count: 'Article Count' value: 'state'
name: 'State'
},
{
value: 'priority'
name: 'Priority'
},
{
value: 'group'
name: 'Group'
},
{
value: 'owner'
name: 'Owner'
},
{
value: 'created_at'
name: 'Age'
},
{
value: 'last_contact'
name: 'Last Contact'
},
{
value: 'last_contact_agent'
name: 'Last Contact Agent'
},
{
value: 'last_contact_customer'
name: 'Last Contact Customer'
},
{
value: 'first_response'
name: 'First Response'
},
{
value: 'close_time'
name: 'Close Time'
},
{
value: 'article_count'
name: 'Article Count'
},
]
class: 'medium' class: 'medium'
}, },

View file

@ -5,19 +5,21 @@ class App.Ticket extends App.Model
@configure_attributes = [ @configure_attributes = [
{ name: 'number', display: '#', tag: 'input', type: 'text', limit: 100, null: true, read_only: true, style: 'width: 8%' }, { name: 'number', display: '#', tag: 'input', type: 'text', limit: 100, null: true, read_only: true, style: 'width: 8%' },
{ name: 'customer_id', display: 'Customer', tag: 'input', type: 'text', limit: 100, null: false, class: 'span8', autocapitalize: false, help: 'Select the customer of the Ticket or create one.', link: '<a href="" class="customer_new">&raquo;</a>' }, { name: 'customer_id', display: 'Customer', tag: 'input', type: 'text', limit: 100, null: false, class: 'span8', autocapitalize: false, help: 'Select the customer of the Ticket or create one.', link: '<a href="" class="customer_new">&raquo;</a>' },
{ name: 'organization_id', display: 'Organization', tagreadonly: 1 },
{ name: 'group_id', display: 'Group', tag: 'select', multiple: false, limit: 100, null: false, class: 'span8', relation: 'Group', style: 'width: 10%' }, { name: 'group_id', display: 'Group', tag: 'select', multiple: false, limit: 100, null: false, class: 'span8', relation: 'Group', style: 'width: 10%' },
{ name: 'owner_id', display: 'Owner', tag: 'select', multiple: false, limit: 100, null: true, class: 'span8', relation: 'User', style: 'width: 12%' }, { name: 'owner_id', display: 'Owner', tag: 'select', multiple: false, limit: 100, null: true, class: 'span8', relation: 'User', style: 'width: 12%' },
{ name: 'title', display: 'Title', tag: 'input', type: 'text', limit: 100, null: false, class: 'span8' }, { name: 'title', display: 'Title', tag: 'input', type: 'text', limit: 100, null: false, class: 'span8' },
{ name: 'state_id', display: 'State', tag: 'select', multiple: false, null: false, relation: 'TicketState', default: 'new', class: 'medium', style: 'width: 12%' }, { name: 'state_id', display: 'State', tag: 'select', multiple: false, null: false, relation: 'TicketState', default: 'new', class: 'medium', style: 'width: 12%' },
{ name: 'priority_id', display: 'Priority', tag: 'select', multiple: false, null: false, relation: 'TicketPriority', default: '2 normal', class: 'medium', style: 'width: 12%' }, { name: 'priority_id', display: 'Priority', tag: 'select', multiple: false, null: false, relation: 'TicketPriority', default: '2 normal', class: 'medium', style: 'width: 12%' },
{ name: 'created_at', display: 'Created', tag: 'time', style: 'width: 12%' }, { name: 'last_contact', display: 'Last contact', type: 'time', null: true, style: 'width: 12%' },
{ name: 'last_contact', display: 'Last contact', tag: 'time', null: true, style: 'width: 12%' }, { name: 'last_contact_agent', display: 'Last contact (Agent)', type: 'time', null: true, style: 'width: 12%' },
{ name: 'last_contact_agent', display: 'Last contact (Agent)', tag: 'time', null: true, style: 'width: 12%' }, { name: 'last_contact_customer', display: 'Last contact (Customer)', type: 'time', null: true, style: 'width: 12%' },
{ name: 'last_contact_customer', display: 'Last contact (Customer)', tag: 'time', null: true, style: 'width: 12%' }, { name: 'first_response', display: 'First response', type: 'time', null: true, style: 'width: 12%' },
{ name: 'first_response', display: 'First response', tag: 'time', null: true, style: 'width: 12%' }, { name: 'close_time', display: 'Close time', type: 'time', null: true, style: 'width: 12%' },
{ name: 'close_time', display: 'Close time', tag: 'time', null: true, style: 'width: 12%' }, { name: 'escalation_time', display: 'Escalation in', type: 'time', null: true, style: 'width: 12%', class: 'escalation' },
{ name: 'escalation_time', display: 'Escalation in', tag: 'time', null: true, style: 'width: 12%' },
{ name: 'article_count', display: 'Article#', style: 'width: 12%' }, { name: 'article_count', display: 'Article#', style: 'width: 12%' },
{ name: 'created_at', display: 'Created', type: 'time', style: 'width: 12%', readonly: 1 },
{ name: 'updated_at', display: 'Updated', type: 'time', style: 'width: 12%', readonly: 1 },
] ]
uiUrl: -> uiUrl: ->
@ -41,6 +43,13 @@ class App.Ticket extends App.Model
else else
data.customer = App.User.find( data.customer_id ) data.customer = App.User.find( data.customer_id )
# organization_id
if data.organization_id
if !App.Organization.exists( data.organization_id )
console.error("Can't find user for data.organization_id #{data.organization_id} for ticket #{data.id}")
else
data.organization = App.Organization.find( data.organization_id )
# owner # owner
if data.owner_id if data.owner_id
if !App.User.exists( data.owner_id ) if !App.User.exists( data.owner_id )

View file

@ -2,3 +2,4 @@ class App.TicketArticleSender extends App.Model
@configure 'TicketArticleSender', 'name', 'updated_at' @configure 'TicketArticleSender', 'name', 'updated_at'
@extend Spine.Model.Ajax @extend Spine.Model.Ajax
@url: @apiPath + '/ticket_article_senders' @url: @apiPath + '/ticket_article_senders'
@configure_translate = true

View file

@ -2,3 +2,4 @@ class App.TicketArticleType extends App.Model
@configure 'TicketArticleType', 'name', 'updated_at' @configure 'TicketArticleType', 'name', 'updated_at'
@extend Spine.Model.Ajax @extend Spine.Model.Ajax
@url: @apiPath + '/ticket_article_types' @url: @apiPath + '/ticket_article_types'
@configure_translate = true

View file

@ -2,3 +2,13 @@ class App.TicketPriority extends App.Model
@configure 'TicketPriority', 'name', 'note', 'active', 'updated_at' @configure 'TicketPriority', 'name', 'note', 'active', 'updated_at'
@extend Spine.Model.Ajax @extend Spine.Model.Ajax
@url: @apiPath + '/ticket_priorities' @url: @apiPath + '/ticket_priorities'
@configure_attributes = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, translate: true },
{ name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false },
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 },
{ name: 'created_at', display: 'Created', type: 'time', readonly: 1 },
]
@configure_translate = true
@configure_overview = [
'name',
]

View file

@ -2,3 +2,13 @@ class App.TicketState extends App.Model
@configure 'TicketState', 'name', 'note', 'active' @configure 'TicketState', 'name', 'note', 'active'
@extend Spine.Model.Ajax @extend Spine.Model.Ajax
@url: @apiPath + '/ticket_states' @url: @apiPath + '/ticket_states'
@configure_attributes = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, translate: true },
{ name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false },
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 },
{ name: 'created_at', display: 'Created', type: 'time', readonly: 1 },
]
@configure_translate = true
@configure_overview = [
'name',
]

View file

@ -7,8 +7,8 @@
<% if @radio: %> <% if @radio: %>
<th style="width: 22px"></th> <th style="width: 22px"></th>
<% end %> <% end %>
<% for row in @header: %> <% for item in @header: %>
<th <% if row.style: %>style="<%= row.style %>"<% end %>><%- @T( row.display ) %></th> <th <% if item.style: %>style="<%= item.style %>"<% end %>><%- @T( item.display ) %></th>
<% end %> <% end %>
<% if @destroy: %> <% if @destroy: %>
<th class="span2"><%- @T('Delete') %></th> <th class="span2"><%- @T('Delete') %></th>
@ -16,17 +16,25 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<% position = 0 %>
<% length = @header.length %> <% length = @header.length %>
<% if @checkbox || @radio: %> <% if @checkbox || @radio: %>
<% length++ %> <% length++ %>
<% end %> <% end %>
<% position = 0 %>
<% groupLast = '' %> <% groupLast = '' %>
<% for object in @objects: %> <% for object in @objects: %>
<% if @groupBy: %> <% if @groupBy: %>
<% if groupLast isnt object[@groupBy.id]: %> <% if object[@groupBy] && object[@groupBy].displayName: %>
<tr class=""><td colspan="<%= length %>"><b><%- @P( object[@groupBy.name] ) %></b></td></tr> <% groupByName = object[@groupBy].displayName() %>
<% groupLast = object[@groupBy.id] %> <% if object[@groupBy].translate(): %>
<% groupByName = @T(groupByName) %>
<% end %>
<% else: %>
<% groupByName = object[@groupBy] || '-' %>
<% end %>
<% if groupLast isnt groupByName: %>
<tr class=""><td colspan="<%= length %>"><b><%- @P( groupByName ) %></b></td></tr>
<% groupLast = groupByName %>
<% end %> <% end %>
<% end %> <% end %>
<% position++ %> <% position++ %>
@ -37,33 +45,53 @@
<% if @radio: %> <% if @radio: %>
<td><input type="radio" value="<%= object.id %>" name="radio"/></td> <td><input type="radio" value="<%= object.id %>" name="radio"/></td>
<% end %> <% end %>
<% for row in @overview: %> <% for item in @header: %>
<% displayName = @P( object[row.name], row ) %> <% translation = false %>
<% if row.translate: %><% displayName = @T( displayName ) %><% end %> <% value = object[item.name] %>
<% if row.title: %> <% item_id = item.name.substr(item.name.length-3, item.name.length) %>
<% displayNameTitle = displayName %> <% if item_id is '_id' && object[ item.name.substr(0, item.name.length-3) ]: %>
<% if object[row.title]: %> <% value = object[ item.name.substr(0, item.name.length-3) ] %>
<% displayNameTitle = @P( object[row.title], row ) %> <% refObject = object[ item.name.substr(0, item.name.length-3) ] %>
<% end %>
<% end %> <% end %>
<td <% if row.title: %>title="<%= displayNameTitle %>"<% end %>> <% if !value: %>
<% if row.type is 'link': %> <% parts = item.name.split '::' %>
<a href="#" data-type="<%= row.dataType %>" <% if row.class: %>class="<%= row.class %>"<% end %>> <% if parts[0] && parts[1] && object[ parts[0] ]: %>
<% else: %> <% value = object[ parts[0] ][ parts[1] ] %>
<span <% if row.class: %>class="<%= row.class %>"<% end %> <% if row.data && row.data.id: %>data-id="<%= object[row.name].id %>"<% end %>> <% end %>
<% end %> <% end %>
<% if row.translate || row.callback: %><%- displayName %><% else: %><%= displayName %><% end %> <% if value && value.displayNameLong : %>
<% if row.type is 'link': %></a><% else: %></span><% end %> <% translation = true %>
<% value = value.displayNameLong() %>
<% else if value && value.displayName : %>
<% translation = true %>
<% value = value.displayName() %>
<% end %>
<% item_clone = item %>
<% if @callbacks: %>
<% for attribute, callbacksAll of @callbacks: %>
<% if attribute is item.name || attribute is item_id: %>
<% for callback in callbacksAll: %>
<% callback( value, object, item_clone, @header, refObject ) %>
<% end %>
<% end %>
<% end %>
<% end %>
<% #console.log('HH', item_clone.name, item_clone.type, item_clone.translate, item_clone, object.translate(), refObject, translation) %>
<td>
<% if item_clone.link: %><a href="<%- item_clone.link %>"><% end %>
<% if item_clone.translate || ( translation && !refObject && object.translate && object.translate() ) || ( translation && refObject && refObject.translate && refObject.translate() ) : %>
<span <% if item_clone.class: %>class="<%= item_clone.class %>"<% end %>><%- @T( @P( value, item_clone ) ) %></span>
<% else if item_clone.type is 'time': %>
<span class="humanTimeFromNow <% if item_clone.class: %><%= item_clone.class %><% end %>" data-time="<%- object[item_clone.name] %>">?</span>
<% else: %>
<span <% if item_clone.class: %>class="<%= item_clone.class %>"<% end %> <% if item_clone.title: %>title="<%= item_clone.title %>"<% end %> <% if item_clone.data: %>data-id="<%= item_clone.data.id %><% end %>"><%= @P(value) %></span>
<% end %>
<% if item_clone.link: %></a><% end %>
</td> </td>
<% end %> <% end %>
<!-- <% if @destroy: %>
<td><%= object.updated_at %></td> <td><a href="#" class="glyphicon glyphicon-trash" data-type="destroy"></a></td>
--> <% end %>
<% if @destroy: %>
<td>
<a href="#" class="glyphicon glyphicon-trash" data-type="destroy"></a>
</td>
<% end %>
</tr> </tr>
<% end %> <% end %>
</tbody> </tbody>

View file

@ -16,6 +16,13 @@ class TestsController < ApplicationController
end end
end end
# GET /tests/table
def table
respond_to do |format|
format.html # index.html.erb
end
end
# GET /test/wait # GET /test/wait
def wait def wait
sleep params[:sec].to_i sleep params[:sec].to_i

View file

@ -0,0 +1,17 @@
<link rel="stylesheet" href="/assets/tests/qunit-1.10.0.css">
<script src="/assets/tests/qunit-1.10.0.js"></script>
<script src="/assets/tests/table.js"></script>
<style type="text/css">
body {
padding-top: 0px;
}
</style>
<script type="text/javascript">
</script>
<div id="qunit"></div>
<div id="table"></div>

View file

@ -2,6 +2,7 @@ Zammad::Application.routes.draw do
match '/tests-core', :to => 'tests#core', :via => :get match '/tests-core', :to => 'tests#core', :via => :get
match '/tests-form', :to => 'tests#form', :via => :get match '/tests-form', :to => 'tests#form', :via => :get
match '/tests-table', :to => 'tests#table', :via => :get
match '/tests/wait/:sec', :to => 'tests#wait', :via => :get match '/tests/wait/:sec', :to => 'tests#wait', :via => :get
end end

View file

@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
require 'browser_test_helper' require 'browser_test_helper'
class UnitTest < TestCase class AAbUnitTest < TestCase
def test_core def test_core
tests = [ tests = [
{ {