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

@ -169,20 +169,14 @@ class App.ControllerGenericIndex extends App.Controller
objects: objects
overview: overview
attributes: attributes
groupBy: 'state'
bindRow:
events:
'click': @edit
)
binds = {}
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) =>
edit: (id, e) =>
e.preventDefault()
item = $(e.target).item( App[ @genericObject ] )
item = App[ @genericObject ].find(id)
if @editCallback
@editCallback(item)

View file

@ -4,48 +4,64 @@ class App.ControllerTable extends App.Controller
@[key] = value
@table = @tableGen(params)
if @el
@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(
header: ['Host', 'User', 'Adapter', 'Active']
overview: ['host', 'user', 'adapter', 'active']
model: App.Channel
objects: data
groupBy: 'group'
checkbox: 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) ->
overview = data.overview || data.model.configure_overview || []
attributes = data.attributes || data.model.configure_attributes || {}
header = data.header
destroy = data.model.configure_delete
# check if table is empty
@ -53,108 +69,132 @@ class App.ControllerTable extends App.Controller
table = '<p>-' + App.i18n.translateContent( 'none' ) + '-</p>'
return $(table)
# define table header
if header
header_new = []
for key in header
header_new.push {
display: key
}
header = header_new
else if !data.overview_extended
# group by
if data.groupBy
# remove group by attribute from header
overview = _.filter(
overview
(item) =>
return item if item isnt data.groupBy
return
)
# get new order
groupObjects = _.groupBy(
data.objects
(item) =>
return '' if !item[data.groupBy]
return item[data.groupBy].displayName() if item[data.groupBy].displayName
item[data.groupBy]
)
groupOrder = []
for group, value of groupObjects
groupOrder.push group
# sort new groups
groupOrder = _.sortBy(
groupOrder
(item) =>
item
)
# create new data array
data.objects = []
for group in groupOrder
data.objects = data.objects.concat groupObjects[group]
groupObjects[group] = [] # release old array
# get header data
header = []
for row in overview
found = false
if attributes
for item in overview
headerFound = false
for attribute in attributes
if row is attribute.name
found = true
if attribute.name is item
headerFound = 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
dataTypesForCols = []
for row in overview
if !_.isEmpty(attributes)
for attribute in attributes
found = false
if row is attribute.name
found = true
dataTypesAttribute = _.clone(attribute)
else if row + '_id' is attribute.name
found = true
dataTypesAttribute = _.clone(attribute)
dataTypesAttribute['name'] = row
if found
dataTypesAttribute['type'] = 'link'
if !dataTypesAttribute['dataType']
dataTypesAttribute['dataType'] = 'edit'
dataTypesForCols.push dataTypesAttribute
else
dataTypesForCols.push {
name: row
type: 'link'
dataType: 'edit'
}
# extended table format
if data.overview_extended
if !header
header = []
for row in data.overview_extended
for attribute in attributes
if row.name is attribute.name
header.push attribute
else
rowWithoutId = row.name + '_id'
if rowWithoutId is attribute.name
rowWithoutId = item + '_id'
if attribute.name is rowWithoutId
headerFound = true
header.push attribute
dataTypesForCols = data.overview_extended
# generate content data
for object in data.objects
# check if info for each col. is already there
for row in dataTypesForCols
# 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
# get content
@log 'debug', 'table', 'header', header, 'overview', 'objects', data.objects
table = App.view('generic/table')(
header: header
overview: dataTypesForCols
objects: data.objects
checkbox: data.checkbox
radio: data.radio
groupBy: data.groupBy
destroy: destroy
callbacks: data.callbackAttributes
)
# convert to jquery object
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
if data.model && destroy
table.delegate('[data-type="destroy"]', 'click', (e) ->
e.stopPropagation()
e.preventDefault()
itemId = $(e.target).parents('tr').data('id')
item = data.model.find(itemId)
@ -165,11 +205,24 @@ class App.ControllerTable extends App.Controller
# enable checkbox bulk selection
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')
$(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
$(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
constructor: ->
super
@ -46,7 +41,6 @@ class App.ChannelEmail extends App.ControllerTabs
class App.ChannelEmailFilter extends App.Controller
events:
'click [data-type=new]': 'new'
'click [data-type=edit]': 'edit'
constructor: ->
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>' )
new App.ControllerTable(
el: template.find('.overview'),
model: App.PostmasterFilter,
objects: data,
el: template.find('.overview')
model: App.PostmasterFilter
objects: data
bindRow:
events:
'click': @edit
)
@html template
@ -69,10 +66,9 @@ class App.ChannelEmailFilter extends App.Controller
e.preventDefault()
new App.ChannelEmailFilterEdit( {} )
edit: (e) =>
edit: (id, e) =>
e.preventDefault()
item = $(e.target).item( App.PostmasterFilter )
new App.ChannelEmailFilterEdit( object: item )
new App.ChannelEmailFilterEdit( object: App.PostmasterFilter.find(id) )
class App.ChannelEmailFilterEdit extends App.ControllerModal
constructor: ->
@ -135,7 +131,6 @@ class App.ChannelEmailFilterEdit extends App.ControllerModal
class App.ChannelEmailAddress extends App.Controller
events:
'click [data-type=new]': 'new'
'click [data-type=edit]': 'edit'
constructor: ->
super
@ -151,6 +146,9 @@ class App.ChannelEmailAddress extends App.Controller
el: template.find('.overview')
model: App.EmailAddress
objects: data
bindRow:
events:
'click': @edit
)
@html template
@ -159,9 +157,9 @@ class App.ChannelEmailAddress extends App.Controller
e.preventDefault()
new App.ChannelEmailAddressEdit( {} )
edit: (e) =>
edit: (id, e) =>
e.preventDefault()
item = $(e.target).item( App.EmailAddress )
item = App.EmailAddress.find(id)
new App.ChannelEmailAddressEdit( object: item )
class App.ChannelEmailAddressEdit extends App.ControllerModal
@ -223,7 +221,6 @@ class App.ChannelEmailAddressEdit extends App.ControllerModal
class App.ChannelEmailSignature extends App.Controller
events:
'click [data-type=new]': 'new'
'click [data-type=edit]': 'edit'
constructor: ->
super
@ -238,17 +235,19 @@ class App.ChannelEmailSignature extends App.Controller
el: template.find('.overview')
model: App.Signature
objects: data
bindRow:
events:
'click': @edit
)
@html template
new: (e) =>
e.preventDefault()
new App.ChannelEmailSignatureEdit( {} )
edit: (e) =>
edit: (id, e) =>
e.preventDefault()
item = $(e.target).item( App.Signature )
item = App.Signature.find(id)
new App.ChannelEmailSignatureEdit( object: item )
class App.ChannelEmailSignatureEdit extends App.ControllerModal
@ -310,31 +309,23 @@ class App.ChannelEmailSignatureEdit extends App.ControllerModal
class App.ChannelEmailInbound extends App.Controller
events:
'click [data-type=new]': 'new'
'click [data-type=edit]': 'edit'
constructor: ->
super
App.Channel.subscribe( @render, initFetch: true )
render: =>
channels = App.Channel.all()
data = []
for channel in channels
if channel.area is 'Email::Inbound'
channel.host = channel.options['host']
channel.user = channel.options['user']
data.push channel
channels = App.Channel.search( filter: { area: 'Email::Inbound' } )
template = $( '<div><div class="overview"></div><a data-type="new" class="btn btn-default">' + App.i18n.translateContent('New') + '</a></div>' )
new App.ControllerTable(
el: template.find('.overview'),
header: ['Host', 'User', 'Adapter', 'Active'],
overview: ['host', 'user', 'adapter', 'active'],
model: App.Channel,
objects: data,
el: template.find('.overview')
model: App.Channel
objects: channels
bindRow:
events:
'click': @edit
)
@html template
@ -342,9 +333,9 @@ class App.ChannelEmailInbound extends App.Controller
e.preventDefault()
new App.ChannelEmailInboundEdit( {} )
edit: (e) =>
edit: (id, e) =>
e.preventDefault()
item = $(e.target).item( App.Channel )
item = App.Channel.find(id)
new App.ChannelEmailInboundEdit( object: item )
@ -354,29 +345,13 @@ class App.ChannelEmailInboundEdit extends App.ControllerModal
@render(@object)
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
@html App.view('generic/admin/edit')(
head: 'Email Channel'
)
@form = new App.ControllerForm(
el: @el.find('#object_edit')
model: { configure_attributes: configure_attributes, className: '' }
model: App.Channel
autofocus: true
)
else
@ -385,7 +360,7 @@ class App.ChannelEmailInboundEdit extends App.ControllerModal
)
@form = new App.ControllerForm(
el: @el.find('#object_new')
model: { configure_attributes: configure_attributes, className: '' }
model: App.Channel
autofocus: true
)
@modalShow()
@ -395,21 +370,10 @@ class App.ChannelEmailInboundEdit extends App.ControllerModal
# get params
params = @formParam(e.target)
params['area'] = 'Email::Inbound'
object = @object || new App.Channel
object.load(
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']
)
object.load(params)
# validate form
errors = @form.validate( params )
@ -463,7 +427,7 @@ class App.ChannelEmailOutbound extends App.Controller
channel_used = channel
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(
el: @el.find('#form-email-adapter'),
@ -476,10 +440,10 @@ class App.ChannelEmailOutbound extends App.Controller
if adapter_used is 'SMTP'
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: 'user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, class: 'span4', 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: '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: '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, autocapitalize: false, default: (channel_used['options']&&channel_used['options']['user']) },
{ 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' } , 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) },
]
@form = new App.ControllerForm(

View file

@ -1,6 +1,5 @@
class App.DashboardTicket extends App.Controller
events:
'click [data-type=edit]': 'zoom'
'click [data-type=settings]': 'settings'
'click [data-type=page]': 'page'
@ -18,11 +17,11 @@ class App.DashboardTicket extends App.Controller
# render
@fetch()
fetch: =>
fetch: (force) =>
# use cache of first page
cache = App.Store.get( @key )
if cache
if !force && cache
@load( cache )
# 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.bind 'local:rerender', (record) =>
@log 'notice', 'rerender...', record
data.overview = record
@render(data)
App.Overview.unbind('local:refetch')
App.Overview.bind 'local:refetch', (record) =>
@log 'notice', 'refetch...', record
@fetch()
@fetch(true)
@render( data )
@ -72,8 +72,8 @@ class App.DashboardTicket extends App.Controller
@overview = data.overview
@tickets_count = data.tickets_count
@ticket_ids = data.ticket_ids
# FIXME 10
pages_total = parseInt( ( @tickets_count / 10 ) + 0.99999 ) || 1
per_page = @overview.view.per_page || 10
pages_total = parseInt( ( @tickets_count / per_page ) + 0.99999 ) || 1
html = App.view('dashboard/ticket')(
overview: @overview,
pages_total: pages_total,
@ -92,13 +92,39 @@ class App.DashboardTicket extends App.Controller
if @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(
overview: @overview.view.d
el: html.find('.table-overview'),
overview_extended: shown_all_attributes,
model: App.Ticket,
model: App.Ticket
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
@ -122,8 +148,9 @@ class App.DashboardTicket extends App.Controller
settings: (e) =>
e.preventDefault()
new Settings(
overview: App.Overview.find(@overview.id)
new App.OverviewSettings(
overview_id: @overview.id
view_mode: 'd'
)
page: (e) =>
@ -132,139 +159,3 @@ class App.DashboardTicket extends App.Controller
@start_page = id
@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
constructor: ->
super
@render()
render: ->
@ -55,11 +54,11 @@ class Table extends App.ControllerContent
# render
@fetch()
fetch: =>
fetch: (force) =>
# use cache of first page
cache = App.Store.get( @key )
if cache
if !force && cache
@load(cache)
# 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.bind 'local:refetch', (record) =>
@log 'notice', 'refetch...', record
@fetch()
@fetch(true)
@ticket_list_show = []
for ticket_id in @ticket_ids
@ -123,6 +122,8 @@ class Table extends App.ControllerContent
render: ->
# if customer and no ticket exists, show the following message only
if !@ticket_list_show[0] && @isRole('Customer')
@html App.view('customer_not_ticket_exists')()
@ -131,6 +132,7 @@ class Table extends App.ControllerContent
@selected = @bulkGetSelected()
# set page title
@overview = App.Overview.find( @overview.id )
@title @overview.name
# render init page
@ -182,27 +184,51 @@ class Table extends App.ControllerContent
)
@el.find('.table-overview').append(table)
else
shown_all_attributes = @ticketTableAttributes( App.Overview.find( @overview.id ).view.s )
groupBy = undefined
if @overview.group_by
group_by =
name: @overview.group_by
id: @overview.group_by + '_id'
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
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(
overview: @overview.view.s
el: @el.find('.table-overview')
overview_extended: shown_all_attributes
model: App.Ticket
objects: @ticket_list_show
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 )
@ -215,12 +241,13 @@ class Table extends App.ControllerContent
# start bulk action observ
@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')
# show/hide bulk action
@el.find('.table-overview').delegate('[name="bulk"], [name="bulk_all"]', 'click', (e) =>
if @el.find('.table-overview').find('[name="bulk"]:checked').length == 0
@el.find('.table-overview').delegate('input[name="bulk"], input[name="bulk_all"]', 'click', (e) =>
console.log('YES')
if @el.find('.table-overview').find('input[name="bulk"]:checked').length == 0
# hide
@el.find('.bulk-action').addClass('hide')
@ -324,6 +351,7 @@ class Table extends App.ControllerContent
@fetch()
)
)
@el.find('.table-overview').find('[name="bulk"]:checked').prop('checked', false)
App.Event.trigger 'notify', {
type: 'success'
msg: App.i18n.translateContent('Bulk-Action executed!')
@ -343,14 +371,15 @@ class Table extends App.ControllerContent
settings: (e) =>
e.preventDefault()
new Settings(
overview: App.Overview.find(@overview.id),
view_mode: @view_mode,
new App.OverviewSettings(
overview_id: @overview.id
view_mode: @view_mode
)
class Settings extends App.ControllerModal
class App.OverviewSettings extends App.ControllerModal
constructor: ->
super
@overview = App.Overview.find(@overview_id)
@render()
render: ->
@ -358,56 +387,101 @@ class Settings extends App.ControllerModal
@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[@view_mode].per_page
options:
15: 15
20: 20
25: 25
30: 30
35: 35
class: 'medium'
# item_class: 'pull-left'
@configure_attributes_article = []
if @view_mode is 'd'
@configure_attributes_article.push({
name: 'view::per_page',
display: 'Items per page',
tag: 'select',
multiple: false,
null: false,
default: @overview.view.per_page
options: {
5: ' 5'
10: '10'
15: '15'
20: '20'
25: '25'
},
{
name: 'attributes'
class: 'medium',
})
@configure_attributes_article.push({
name: "view::#{@view_mode}"
display: 'Attributes'
tag: 'checkbox'
default: @overview.view[@view_mode]
null: false
translate: true
options:
# true: 'internal'
# false: 'public'
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'
options: [
{
value: 'number'
name: 'Number'
},
{
value: 'title'
name: 'Title'
},
{
value: 'customer'
name: 'Customer'
},
{
value: 'organization'
name: 'Organization'
},
{
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: 'escalation_time'
name: 'Escalation in'
},
{
value: 'article_count'
name: 'Article Count'
},
]
class: 'medium'
},
{
name: 'order_by'
name: 'order::by'
display: 'Order'
tag: 'select'
default: @overview.order.by
@ -417,6 +491,7 @@ class Settings extends App.ControllerModal
number: 'Number'
title: 'Title'
customer: 'Customer'
organization: 'Organization'
state: 'State'
priority: 'Priority'
group: 'Group'
@ -432,7 +507,7 @@ class Settings extends App.ControllerModal
class: 'medium'
},
{
name: 'order_by_direction'
name: 'order::direction'
display: 'Direction'
tag: 'select'
default: @overview.order.direction
@ -453,24 +528,13 @@ class Settings extends App.ControllerModal
translate: true
options:
customer: 'Customer'
organization: 'Organization'
state: 'State'
priority: 'Priority'
group: 'Group'
owner: 'Owner'
class: 'medium'
},
# {
# name: 'condition',
# display: 'Conditions',
# tag: 'select',
# multiple: false,
# null: false,
# relation: 'TicketArticleType',
# default: '9',
# class: 'medium',
# item_class: 'pull-left',
# },
]
})
new App.ControllerForm(
el: @el.find('#form-setting')
@ -486,19 +550,18 @@ class Settings extends App.ControllerModal
# check if refetch is needed
@reload_needed = 0
if @overview.order['by'] isnt params['order_by']
@overview.order['by'] = params['order_by']
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']
if @overview.order.direction isnt params.order.direction
@overview.order.direction = params.order.direction
@reload_needed = 1
if @overview['group_by'] isnt params['group_by']
@overview['group_by'] = params['group_by']
@reload_needed = 1
for key, value of params.view
@overview.view[key] = value
@overview.view[@view_mode] = params['attributes']
@overview.group_by = params.group_by
@overview.save(
done: =>

View file

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

View file

@ -16,6 +16,9 @@ class App.Model extends Spine.Model
uiUrl: ->
'#'
translate: ->
App[ @constructor.className ].configure_translate
objectDisplayName: ->
@constructor.className
@ -69,10 +72,23 @@ class App.Model extends Spine.Model
# check required // if null is defined && null is false
if 'null' of attribute && !attribute[null]
# check :: fields
parts = attribute.name.split '::'
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
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"] = ''
# return error object
return errors if !_.isEmpty(errors)
if !_.isEmpty(errors)
console.log 'error', 'validation vailed', errors
return errors
# return no errors
return
@ -327,5 +345,3 @@ class App.Model extends Spine.Model
return
)
collection

View file

@ -3,3 +3,24 @@ class App.Channel extends App.Model
@extend Spine.Model.Ajax
@url: @apiPath + '/channels'
@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']
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'
article_count: 'Article Count'
options: [
{
value: 'number'
name: 'Number'
},
{
value: 'title'
name: 'Title'
},
{
value: 'customer'
name: 'Customer'
},
{
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'
},

View file

@ -5,19 +5,21 @@ class App.Ticket extends App.Model
@configure_attributes = [
{ 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: '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: '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: '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: 'created_at', display: 'Created', tag: 'time', style: 'width: 12%' },
{ name: 'last_contact', display: 'Last contact', tag: '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)', tag: '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', tag: 'time', null: true, style: 'width: 12%' },
{ name: 'escalation_time', display: 'Escalation in', tag: 'time', null: true, style: 'width: 12%' },
{ name: 'last_contact', display: 'Last contact', type: 'time', null: true, style: 'width: 12%' },
{ name: 'last_contact_agent', display: 'Last contact (Agent)', type: 'time', null: true, style: 'width: 12%' },
{ name: 'last_contact_customer', display: 'Last contact (Customer)', type: 'time', null: true, style: 'width: 12%' },
{ name: 'first_response', display: 'First response', type: 'time', null: true, style: 'width: 12%' },
{ name: 'close_time', display: 'Close time', type: 'time', null: true, style: 'width: 12%' },
{ name: 'escalation_time', display: 'Escalation in', type: 'time', null: true, style: 'width: 12%', class: 'escalation' },
{ 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: ->
@ -41,6 +43,13 @@ class App.Ticket extends App.Model
else
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
if 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'
@extend Spine.Model.Ajax
@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'
@extend Spine.Model.Ajax
@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'
@extend Spine.Model.Ajax
@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'
@extend Spine.Model.Ajax
@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: %>
<th style="width: 22px"></th>
<% end %>
<% for row in @header: %>
<th <% if row.style: %>style="<%= row.style %>"<% end %>><%- @T( row.display ) %></th>
<% for item in @header: %>
<th <% if item.style: %>style="<%= item.style %>"<% end %>><%- @T( item.display ) %></th>
<% end %>
<% if @destroy: %>
<th class="span2"><%- @T('Delete') %></th>
@ -16,17 +16,25 @@
</tr>
</thead>
<tbody>
<% position = 0 %>
<% length = @header.length %>
<% if @checkbox || @radio: %>
<% length++ %>
<% end %>
<% position = 0 %>
<% groupLast = '' %>
<% for object in @objects: %>
<% if @groupBy: %>
<% if groupLast isnt object[@groupBy.id]: %>
<tr class=""><td colspan="<%= length %>"><b><%- @P( object[@groupBy.name] ) %></b></td></tr>
<% groupLast = object[@groupBy.id] %>
<% if object[@groupBy] && object[@groupBy].displayName: %>
<% groupByName = object[@groupBy].displayName() %>
<% 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 %>
<% position++ %>
@ -37,33 +45,53 @@
<% if @radio: %>
<td><input type="radio" value="<%= object.id %>" name="radio"/></td>
<% end %>
<% for row in @overview: %>
<% displayName = @P( object[row.name], row ) %>
<% if row.translate: %><% displayName = @T( displayName ) %><% end %>
<% if row.title: %>
<% displayNameTitle = displayName %>
<% if object[row.title]: %>
<% displayNameTitle = @P( object[row.title], row ) %>
<% for item in @header: %>
<% translation = false %>
<% value = object[item.name] %>
<% item_id = item.name.substr(item.name.length-3, item.name.length) %>
<% if item_id is '_id' && object[ item.name.substr(0, item.name.length-3) ]: %>
<% value = object[ item.name.substr(0, item.name.length-3) ] %>
<% refObject = object[ item.name.substr(0, item.name.length-3) ] %>
<% end %>
<% if !value: %>
<% parts = item.name.split '::' %>
<% if parts[0] && parts[1] && object[ parts[0] ]: %>
<% value = object[ parts[0] ][ parts[1] ] %>
<% end %>
<% end %>
<td <% if row.title: %>title="<%= displayNameTitle %>"<% end %>>
<% if row.type is 'link': %>
<a href="#" data-type="<%= row.dataType %>" <% if row.class: %>class="<%= row.class %>"<% end %>>
<% else: %>
<span <% if row.class: %>class="<%= row.class %>"<% end %> <% if row.data && row.data.id: %>data-id="<%= object[row.name].id %>"<% end %>>
<% if value && value.displayNameLong : %>
<% translation = true %>
<% value = value.displayNameLong() %>
<% else if value && value.displayName : %>
<% translation = true %>
<% value = value.displayName() %>
<% end %>
<% if row.translate || row.callback: %><%- displayName %><% else: %><%= displayName %><% end %>
<% if row.type is 'link': %></a><% else: %></span><% end %>
</td>
<% 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 %>
<!--
<td><%= object.updated_at %></td>
-->
<% if @destroy: %>
<% end %>
<% end %>
<% end %>
<% #console.log('HH', item_clone.name, item_clone.type, item_clone.translate, item_clone, object.translate(), refObject, translation) %>
<td>
<a href="#" class="glyphicon glyphicon-trash" data-type="destroy"></a>
<% 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>
<% end %>
<% if @destroy: %>
<td><a href="#" class="glyphicon glyphicon-trash" data-type="destroy"></a></td>
<% end %>
</tr>
<% end %>
</tbody>

View file

@ -16,6 +16,13 @@ class TestsController < ApplicationController
end
end
# GET /tests/table
def table
respond_to do |format|
format.html # index.html.erb
end
end
# GET /test/wait
def wait
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-form', :to => 'tests#form', :via => :get
match '/tests-table', :to => 'tests#table', :via => :get
match '/tests/wait/:sec', :to => 'tests#wait', :via => :get
end

View file

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