Browse Source

Extended App.ControllerTable to just rerender parts of tables. Fixed issue #989 - Zammad UI is completely stuck after selecting overview "All tickets".

Martin Edenhofer 7 years ago
parent
commit
6d077f4ac5

+ 525 - 317
app/assets/javascripts/app/controllers/_application_controller_table.coffee

@@ -1,3 +1,94 @@
+###
+
+  # table 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)
+
+  callbackHeader = (headers) ->
+    console.log('current header is', headers)
+    # add new header item
+    attribute =
+      name: 'some name'
+      display: 'Some Name'
+    headers.push attribute
+    console.log('new header is', headers)
+    headers
+
+  callbackAttributes = (value, object, attribute, header) ->
+    console.log('data of item col', value, object, attribute, header)
+    value = 'New Data To Show'
+    value
+
+  new App.ControllerTable(
+    tableId: 'some_id_to_idientify_user_based_table_preferences'
+    el:       element
+    overview: ['host', 'user', 'adapter', 'active']
+    model:    App.Channel
+    objects:  data
+    groupBy:  'adapter'
+    checkbox: false
+    radio:    false
+    class:    'some-css-class'
+    bindRow:
+      events:
+        'click':      rowClick
+        'mouseover':  rowMouseover
+        'mouseout':   rowMouseout
+        'dblclick':   rowDblClick
+    bindCol:
+      host:
+        events:
+          'click': colClick
+    bindCheckbox:
+      events:
+        'click':      rowClick
+        'mouseover':  rowMouseover
+        'mouseout':   rowMouseout
+        'dblclick':   rowDblClick
+    callbackHeader:   [callbackHeader]
+    callbackAttributes:
+      attributeName: [
+        callbackAttributes
+      ]
+    dndCallback: =>
+      items = @el.find('table > tbody > tr')
+      console.log('all effected items', items)
+  )
+
+  new App.ControllerTable(
+    el:       element
+    overview: ['time', 'area', 'level', 'browser', 'location', 'data']
+    attribute_list: [
+      { name: 'time',     display: 'Time',      tag: 'datetime' },
+      { name: 'area',     display: 'Area',      type: 'text' },
+      { name: 'level',    display: 'Level',     type: 'text' },
+      { name: 'browser',  display: 'Browser',   type: 'text' },
+      { name: 'location', display: 'Location',  type: 'text' },
+      { name: 'data',     display: 'Data',      type: 'text' },
+    ]
+    objects:  data
+  )
+
+###
 class App.ControllerTable extends App.Controller
   minColWidth: 40
   baseColWidth: 130
@@ -10,9 +101,53 @@ class App.ControllerTable extends App.Controller
   elements:
     '.js-tableHead': 'tableHead'
 
-  constructor: (params) ->
+  events:
+    'click .js-sort': 'sortByColumn'
+
+  overviewAttributes: undefined
+  #model:             App.TicketPriority,
+  objects:            []
+  checkbox:           false
+  radio:              false
+  renderState:        undefined
+  groupBy:            undefined
+
+  destroy: false
+
+  columnsLength: undefined
+  headers: undefined
+  headerWidth: {}
+
+  currentRows: []
+
+  orderDirection: 'ASC'
+  orderBy: undefined
+
+  lastOrderDirection: undefined
+  lastOrderBy: undefined
+  lastOverview: undefined
+
+  customOrderDirection: undefined
+  customOrderBy: undefined
+
+  bindCol: {}
+  bindRow: {}
+
+  constructor: ->
     super
 
+    if !@model
+      @model = {}
+    @overviewAttributes ||= @overview || @model.configure_overview || []
+    @attributesListRaw ||= @attribute_list || @model.configure_attributes || {}
+    @attributesList = App.Model.attributesGet(false, @attributesListRaw)
+    console.log('Table', @overviewAttributes, @overview)
+    #@setHeaderWidths = App.Model.setHeaderWidthsGet(false, @attributesList)
+    @destroy    = @model.configure_delete
+
+    throw 'overviewAttributes needed' if _.isEmpty(@overviewAttributes)
+    throw 'attributesList needed' if _.isEmpty(@attributesList)
+
     # apply personal preferences
     data = {}
     if @tableId
@@ -21,20 +156,218 @@ class App.ControllerTable extends App.Controller
         for key, value of data.order
           @[key] = value
 
-    @headerWidth = {}
     if data.headerWidth
       for key, value of data.headerWidth
         @headerWidth[key] = value
 
     @availableWidth = @el.width()
-    @render()
-    $(window).on 'resize.table', @onResize
+
+    @renderQueue()
 
   release: =>
     $(window).off 'resize.table', @onResize
 
+  update: (params) =>
+    console.log('params', params)
+    for key, value of params
+      @[key] = value
+
+    if params.sync is true
+      return @render()
+    @renderQueue()
+
+  renderQueue: =>
+    App.QueueManager.add('tableRender', @render)
+    App.QueueManager.run('tableRender')
+
   render: =>
-    @html @tableGen()
+    if @renderState is undefined
+
+      # check if table is empty
+      if _.isEmpty(@objects)
+        @renderState = 'emptyList'
+        @el.html(@renderEmptyList())
+        $(window).on 'resize.table', @onResize
+        return ['emptyList.new']
+      else
+        @renderState = 'List'
+        @renderTableFull()
+        $(window).on 'resize.table', @onResize
+        return ['fullRender.new']
+    else if @renderState is 'emptyList' && !_.isEmpty(@objects)
+      @renderState = 'List'
+      @renderTableFull()
+      return ['fullRender']
+    else if @renderState isnt 'emptyList' && _.isEmpty(@objects)
+      @renderState = 'emptyList'
+      @el.html(@renderEmptyList())
+      return ['emptyList']
+    else
+
+      # check if header has changed
+      if @tableHeadersHasChanged()
+        @renderTableFull()
+        return ['fullRender.overviewAttributesChanged']
+
+      # check for changes
+      newRows = @renderTableRows(true)
+      removedRows = _.difference(@currentRows, newRows)
+      addedRows = _.difference(newRows, @currentRows)
+
+      # if only rows are removed
+      if _.isEmpty(addedRows) && !_.isEmpty(removedRows) && removedRows.length < 15 && !_.isEmpty(newRows)
+        newCurrentRows = []
+        removePositions = []
+        for position in [0..@currentRows.length-1]
+          if _.contains(removedRows, @currentRows[position])
+            removePositions.push position
+          else
+            newCurrentRows.push @currentRows[position]
+
+        # check if order is still correct
+        if @_isSame(newRows, newCurrentRows) is true
+          for position in removePositions
+            @$("tbody > tr:nth-child(#{position+1})").remove()
+          @currentRows = newCurrentRows
+          console.log('fullRender.contentRemoved', removePositions)
+          return ['fullRender.contentRemoved', removePositions]
+
+      if newRows.length isnt @currentRows.length
+        result = ['fullRender.lenghtChanged', @currentRows.length, newRows.length]
+        @renderTableFull(newRows)
+        console.log('result', result)
+        return result
+
+      # compare rows
+      result = @_isSame(newRows, @currentRows)
+      if result isnt true
+        @renderTableFull(newRows)
+        console.log('result', "fullRender.contentChanged|row(#{result})")
+        return ['fullRender.contentChanged', result]
+
+    console.log('result', 'noChanges')
+    return ['noChanges']
+
+  renderEmptyList: =>
+    App.view('generic/admin/empty')(
+      explanation: @explanation
+    )
+
+  renderTableFull: (rows) =>
+    console.log('renderTableFull', @orderBy, @orderDirection)
+    @tableHeaders()
+    @sortList()
+    bulkIds = @getBulkSelected()
+    container = @renderTableContainer()
+    if !rows
+      rows = @renderTableRows()
+      @currentRows = clone(rows)
+    else
+      @currentRows = clone(rows)
+    container.find('.js-tableBody').html(rows)
+
+    cursorMap =
+      click:    'pointer'
+      dblclick: 'pointer'
+      #mouseover: 'alias'
+
+    # bind col.
+    if !_.isEmpty(@bindCol)
+      for name, item of @bindCol
+        if item.events
+          position = 0
+          if @dndCallback
+            position += 1
+          if @checkbox
+            position += 1
+          hit = false
+
+          for headerName in @headers
+            if !hit
+              position += 1
+            if headerName.name is name || headerName.name is "#{name}_id" || headerName.name is "#{name}_bulkIds"
+              hit = true
+
+          if hit
+            for event, callback of item.events
+              do (container, event, callback) ->
+                if cursorMap[event]
+                  container.find("tbody > tr > td:nth-child(#{position})").css('cursor', cursorMap[event])
+                container.on( event, "tbody > tr > td:nth-child(#{position})",
+                  (e) ->
+                    e.stopPropagation()
+                    id = $(e.target).parents('tr').data('id')
+                    callback(id, e)
+                )
+
+    # bind row
+    if !_.isEmpty(@bindRow)
+      if @bindRow.events
+        for event, callback of @bindRow.events
+          do (container, event, callback) ->
+            if cursorMap[event]
+              container.find('tbody > tr').css( 'cursor', cursorMap[event] )
+            container.on( event, 'tbody > tr',
+              (e) ->
+                id = $(e.target).parents('tr').data('id')
+                callback(id, e)
+            )
+
+    # bind bindCheckbox
+    if @bindCheckbox
+      if @bindCheckbox.events
+        for event, callback of @bindCheckbox.events
+          do (container, event, callback) ->
+            container.delegate('input[name="bulk"]', event, (e) ->
+              e.stopPropagation()
+              id      = $(e.currentTarget).parents('tr').data('id')
+              checked = $(e.currentTarget).prop('checked')
+              callback(id, checked, e)
+            )
+
+    # if we have a personalised table
+    if @tableId
+
+      # enable resize column
+      container.on 'mousedown', '.js-col-resize', @onColResizeMousedown
+      container.on 'click', '.js-col-resize', @stopPropagation
+
+    # enable checkbox bulk selection
+    if @checkbox
+
+      # click first tr>td, catch click
+      container.delegate('tr > td:nth-child(1)', 'click', (e) ->
+        e.stopPropagation()
+      )
+
+      # bind on full bulk click
+      container.delegate('input[name="bulk_all"]', 'change', (e) =>
+        e.stopPropagation()
+        clicks = []
+        if $(e.currentTarget).prop('checked')
+          $(e.currentTarget).parents('table').find('[name="bulk"]').each( ->
+            $element = $(@)
+            return if $element.prop('checked')
+            $element.prop('checked', true)
+            id = $element.parents('tr').data('id')
+            clicks.push [id, true]
+          )
+        else
+          $(e.currentTarget).parents('table').find('[name="bulk"]').each( ->
+            $element = $(@)
+            return if !$element.prop('checked')
+            $element.prop('checked', false)
+            id = $element.parents('tr').data('id')
+            clicks.push [id, false]
+          )
+        return if !@bindCheckbox
+        return if !@bindCheckbox.events
+        return if _.isEmpty(clicks)
+        for event, callback of @bindCheckbox.events
+          if event == 'click' || event == 'change'
+            for click in clicks
+              callback(click..., e)
+      )
 
     if @dndCallback
       dndOptions =
@@ -50,121 +383,79 @@ class App.ControllerTable extends App.Controller
             # Set helper cell sizes to match the original sizes
             $(@).width( originals.eq(index).outerWidth() )
           return helper
-        update:               @dndCallback
-      @el.find('table > tbody').sortable(dndOptions)
-
-  ###
-
-    # table 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)
-
-    callbackHeader = (headers) ->
-      console.log('current header is', headers)
-      # add new header item
-      attribute =
-        name: 'some name'
-        display: 'Some Name'
-      headers.push attribute
-      console.log('new header is', headers)
-      headers
-
-    callbackAttributes = (value, object, attribute, header) ->
-      console.log('data of item col', value, object, attribute, header)
-      value = 'New Data To Show'
-      value
-
-    new App.ControllerTable(
-      tableId: 'some_id_to_idientify_user_based_table_preferences'
-      el:       element
-      overview: ['host', 'user', 'adapter', 'active']
-      model:    App.Channel
-      objects:  data
-      groupBy:  'adapter'
-      checkbox: false
-      radio:    false
-      class:    'some-css-class'
-      bindRow:
-        events:
-          'click':      rowClick
-          'mouseover':  rowMouseover
-          'mouseout':   rowMouseout
-          'dblclick':   rowDblClick
-      bindCol:
-        host:
-          events:
-            'click': colClick
-      bindCheckbox:
-        events:
-          'click':      rowClick
-          'mouseover':  rowMouseover
-          'mouseout':   rowMouseout
-          'dblclick':   rowDblClick
-      callbackHeader:   [callbackHeader]
-      callbackAttributes:
-        attributeName: [
-          callbackAttributes
-        ]
-      dndCallback: =>
-        items = @el.find('table > tbody > tr')
-        console.log('all effected items', items)
+        update: @dndCallback
+      container.find('tbody').sortable(dndOptions)
+
+    @el.html(container)
+    @setBulkSelected(bulkIds)
+
+  renderTableContainer: =>
+    $(App.view('generic/table')(
+      tableId:    @tableId
+      headers:    @headers
+      checkbox:   @checkbox
+      radio:      @radio
+      class:      @class
+      sortable:   @dndCallback
+    ))
+
+  renderTableRows: (sort = false) =>
+    if sort is true
+      @sortList()
+    position = 0
+    columnsLength = @headers.length
+    if @checkbox || @radio
+      columnsLength++
+    groupLast = ''
+    tableBody = []
+    for object in @objects
+      position++
+      if @groupBy
+        groupByName = App.viewPrint(object, @groupBy, @attributesList)
+        if groupLast isnt groupByName
+          groupLast = groupByName
+          tableBody.push @renderTableGroupByRow(object, position, groupByName)
+      tableBody.push @renderTableRow(object, position)
+    tableBody
+
+  renderTableGroupByRow: (object, position, groupByName) =>
+    App.view('generic/table_row_group_by')(
+      position:      position
+      groupByName:   groupByName
+      columnsLength: @columnsLength
     )
 
-    new App.ControllerTable(
-      el:       element
-      overview: ['time', 'area', 'level', 'browser', 'location', 'data']
-      attribute_list: [
-        { name: 'time',     display: 'Time',      tag: 'datetime' },
-        { name: 'area',     display: 'Area',      type: 'text' },
-        { name: 'level',    display: 'Level',     type: 'text' },
-        { name: 'browser',  display: 'Browser',   type: 'text' },
-        { name: 'location', display: 'Location',  type: 'text' },
-        { name: 'data',     display: 'Data',      type: 'text' },
-      ]
-      objects:  data
+  renderTableRow: (object, position) =>
+    App.view('generic/table_row')(
+      headers:    @headers
+      attributes: @attributesList
+      checkbox:   @checkbox
+      radio:      @radio
+      callbacks:  @callbackAttributes
+      sortable:   @dndCallback
+      position:   position
+      object:     object
     )
 
-  ###
+  tableHeadersHasChanged: =>
+    return true if @overviewAttributes isnt @lastOverview
+    false
 
-  tableGen: =>
-    if !@model
-      @model = {}
-    overview   = @overview || @model.configure_overview || []
-    attributes = @attribute_list || @model.configure_attributes || {}
-    attributes = App.Model.attributesGet(false, attributes)
-    destroy    = @model.configure_delete
-
-    # check if table is empty
-    if _.isEmpty(@objects)
-      table = App.view('generic/admin/empty')(
-        explanation: @explanation
-      )
-      return $(table)
+  tableHeaders: =>
+    orderBy = @customOrderBy || @orderBy
+    orderDirection = @customOrderDirection || @orderDirection
+
+    #console.log('LLL', @lastOrderBy, @orderBy, @lastOrderDirection, @orderDirection, @overviewAttributes, @lastOverview)
+    if @headers && @lastOrderBy is orderBy && @lastOrderDirection is orderDirection && !@tableHeadersHasChanged()
+      console.log('tableHeaders: same overviewAttributes just return headers', @headers)
+      return ['headers are the same', @headers]
+    @lastOverview = @overviewAttributes
 
     # get header data
     @headers = []
-    for item in overview
+    for item in @overviewAttributes
       headerFound = false
-      for attributeName, attribute of attributes
+      for attributeName, attribute of @attributesList
 
         # remove group by attribute from header
         if !@groupBy || @groupBy isnt item
@@ -208,8 +499,19 @@ class App.ControllerTable extends App.Controller
                   attribute.displayWidth = value
               @headers.push attribute
 
+
+    # execute header callback
+    if @callbackHeader
+      for callback in @callbackHeader
+        @headers = callback(@headers)
+
+    if @tableId
+      @calculateHeaderWidths()
+
+    throw 'no headers found' if _.isEmpty(@headers)
+
     # add destroy header and col binding
-    if destroy
+    if @destroy
       @headers.push
         name: 'destroy'
         display: 'Delete'
@@ -219,14 +521,63 @@ class App.ControllerTable extends App.Controller
         parentClass: 'js-delete'
         icon: 'trash'
 
-      if !@bindCol
-        @bindCol = {}
       @bindCol['destroy'] =
         events:
           click: @deleteRow
 
-    if @orderDirection && @orderBy && !@groupBy
-      @objects = @sortList(@objects)
+    @columnsLength = @headers.length
+    if @checkbox || @radio
+      @columnsLength++
+    console.log('tableHeaders: new headers', @headers)
+    ['new headers', @headers]
+
+  sortList: =>
+    return if _.isEmpty(@objects)
+
+
+    orderBy = @customOrderBy || @orderBy
+    orderDirection = @customOrderDirection || @orderDirection
+
+    console.log('order', @orderBy, @orderDirection)
+    console.log('customOrder', @customOrderBy, @customOrderDirection)
+
+    return if _.isEmpty(orderBy) && _.isEmpty(@groupBy)
+
+    return if @lastSortedobjects is @objects && @lastOrderDirection is orderDirection && @lastOrderBy is orderBy
+    @lastOrderDirection = orderDirection
+    @lastOrderBy = orderBy
+
+    if orderBy
+      for header in @headers
+        if header.name is orderBy || "#{header.name}_id" is orderBy# || header.name.substring(0, header.name.length - 3) is orderBy
+          localObjects = _.sortBy(
+            @objects
+            (item) ->
+              # if we need to sort translated col.
+              if header.translate
+                return App.i18n.translateInline(item[header.name])
+
+              # if we need to sort by relation name
+              if header.relation
+                if item[header.name]
+                  localItem = App[header.relation].findNative(item[header.name])
+                  if localItem
+                    if localItem.displayName
+                      localItem = localItem.displayName().toLowerCase()
+                    if localItem.name
+                      localItem = localItem.name.toLowerCase()
+                    return localItem
+                return ''
+              item[header.name]
+          )
+          if orderDirection is 'DESC'
+            header.sortOrderIcon = ['arrow-down', 'table-sort-arrow']
+            localObjects = localObjects.reverse()
+          else
+            header.sortOrderIcon = ['arrow-up', 'table-sort-arrow']
+        else
+          header.sortOrderIcon = undefined
+      @objects = localObjects
 
     # group by
     if @groupBy
@@ -237,14 +588,19 @@ class App.ControllerTable extends App.Controller
         group = object[@groupBy]
         if !group
           withId = "#{@groupBy}_id"
-
-          if object[withId] && attributes[withId] && attributes[withId].relation
-            if App[attributes[withId].relation].exists(object[withId])
-              item = App[attributes[withId].relation].findNative(object[withId])
+          if object[withId] && @attributesList[withId] && @attributesList[withId].relation
+            if App[@attributesList[withId].relation].exists(object[withId])
+              item = App[@attributesList[withId].relation].findNative(object[withId])
               if item && item.displayName
                 group = item.displayName().toLowerCase()
+              else if item.name
+                group = item.name.toLowerCase()
         if _.isEmpty(group)
           group = ''
+        if group.displayName
+          group = group.displayName().toLowerCase()
+        else if group.name
+          group = group.name.toLowerCase()
         groupObjects[group] ||= []
         groupObjects[group].push object
 
@@ -254,198 +610,15 @@ class App.ControllerTable extends App.Controller
       groupsSorted = groupsSorted.sort()
 
       # get new order
-      @objects = []
+      localObjects = []
       for group in groupsSorted
-        localObjects = @sortList(groupObjects[group])
-        @objects = @objects.concat localObjects
+        localObjects = localObjects.concat groupObjects[group]
         groupObjects[group] = [] # release old array
 
-    # execute header callback
-    if @callbackHeader
-      for callback in @callbackHeader
-        @headers = callback(@headers)
-
-    if @tableId
-      @calculateHeaderWidths()
-
-    # generate content
-    position = 0
-    columnsLength = @headers.length
-    if @checkbox || @radio
-      columnsLength++
-    groupLast = ''
-    tableBody = ''
-    for object in @objects
-      if @groupBy
-        groupByName = App.viewPrint(object, @groupBy, attributes)
-        if groupLast isnt groupByName
-          groupLast = groupByName
-          tableBody += App.view('generic/table_row_group_by')(
-            position:      position
-            groupByName:   groupByName
-            columnsLength: columnsLength
-          )
-      position++
-      tableBody += App.view('generic/table_row')(
-        headers:    @headers
-        attributes: attributes
-        checkbox:   @checkbox
-        radio:      @radio
-        callbacks:  @callbackAttributes
-        sortable:   @dndCallback
-        position:   position
-        object:     object
-      )
-
-    # generate full table
-    table = App.view('generic/table')(
-      tableId:    @tableId
-      headers:    @headers
-      checkbox:   @checkbox
-      radio:      @radio
-      class:      @class
-      sortable:   @dndCallback
-      tableBody:  tableBody
-    )
-
-    # convert to jquery object
-    table = $(table)
-
-    cursorMap =
-      click:    'pointer'
-      dblclick: 'pointer'
-      #mouseover: 'alias'
-
-    # bind col.
-    if @bindCol
-      for name, item of @bindCol
-        if item.events
-          position = 0
-          if @dndCallback
-            position += 1
-          if @checkbox
-            position += 1
-          hit = false
-
-          for headerName in @headers
-            if !hit
-              position += 1
-            if headerName.name is name || headerName.name is "#{name}_id" || headerName.name is "#{name}_ids"
-              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})").css('cursor', cursorMap[event])
-                table.on( event, "tbody > tr > td:nth-child(#{position})",
-                  (e) ->
-                    e.stopPropagation()
-                    id = $(e.target).parents('tr').data('id')
-                    callback(id, e)
-                )
-
-    # bind row
-    if @bindRow
-      if @bindRow.events
-        for event, callback of @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 @bindCheckbox
-      if @bindCheckbox.events
-        for event, callback of @bindCheckbox.events
-          do (table, event, callback) ->
-            table.delegate('input[name="bulk"]', event, (e) ->
-              e.stopPropagation()
-              id      = $(e.currentTarget).parents('tr').data('id')
-              checked = $(e.currentTarget).prop('checked')
-              callback(id, checked, e)
-            )
-
-    # if we have a personalised table
-    if @tableId
-      # enable resize column
-      table.on 'mousedown', '.js-col-resize', @onColResizeMousedown
-      table.on 'click', '.js-col-resize', @stopPropagation
-
-      # enable sort column
-      table.on 'click', '.js-sort', @sortByColumn
-
-    # enable checkbox bulk selection
-    if @checkbox
-
-      # click first tr>td, catch click
-      table.delegate('tr > td:nth-child(1)', 'click', (e) ->
-        e.stopPropagation()
-      )
-
-      # bind on full bulk click
-      table.delegate('input[name="bulk_all"]', 'change', (e) =>
-        e.stopPropagation()
-        clicks = []
-        if $(e.currentTarget).prop('checked')
-          $(e.currentTarget).parents('table').find('[name="bulk"]').each( ->
-            $element = $(@)
-            return if $element.prop('checked')
-            $element.prop('checked', true)
-            id = $element.parents('tr').data('id')
-            clicks.push [id, true]
-          )
-        else
-          $(e.currentTarget).parents('table').find('[name="bulk"]').each( ->
-            $element = $(@)
-            return if !$element.prop('checked')
-            $element.prop('checked', false)
-            id = $element.parents('tr').data('id')
-            clicks.push [id, false]
-          )
-        return if !@bindCheckbox
-        return if !@bindCheckbox.events
-        return if _.isEmpty(clicks)
-        for event, callback of @bindCheckbox.events
-          if event == 'click' || event == 'change'
-            for click in clicks
-              callback(click..., e)
-      )
-    table
-
-  sortList: (objects) =>
+    @objects = localObjects
+    @lastSortedobjects = localObjects
 
-    for header in @headers
-      if header.name is @orderBy
-        objects = _.sortBy(
-          objects
-          (item) ->
-            # if we need to sort translated col.
-            if header.translate
-              return App.i18n.translateInline(item[header.name])
-
-            # if we need to sort by relation name
-            if header.relation
-              if item[header.name]
-                localItem = App[header.relation].findNative(item[header.name])
-                if localItem && localItem.displayName
-                  localItem = localItem.displayName().toLowerCase()
-                return localItem
-              return ''
-            item[header.name]
-        )
-        if @orderDirection is 'DESC'
-          header.sortOrderIcon = ['arrow-down', 'table-sort-arrow']
-          objects = objects.reverse()
-        else
-          header.sortOrderIcon = ['arrow-up', 'table-sort-arrow']
-      else
-        header.sortOrderIcon = undefined
-    objects
+    localObjects
 
   # bind on delete dialog
   deleteRow: (id, e) =>
@@ -559,30 +732,43 @@ class App.ControllerTable extends App.Controller
 
     # update store and runtime @headerWidth
     @preferencesStore('headerWidth', leftColumnKey, leftWidth)
+    @headerWidth[leftColumnKey] = leftWidth
     _.find(@headers, (column) -> column.name is leftColumnKey).displayWidth = leftWidth
 
     # update store and runtime @headerWidth
     if rightColumnKey
       @preferencesStore('headerWidth', rightColumnKey, rightWidth)
+      @headerWidth[rightColumnKey] = rightWidth
       _.find(@headers, (column) -> column.name is rightColumnKey).displayWidth = rightWidth
 
   sortByColumn: (event) =>
     column = $(event.currentTarget).closest('[data-column-key]').attr('data-column-key')
 
+    orderBy = @customOrderBy || @orderBy
+    orderDirection = @customOrderDirection || @orderDirection
+
     # sort, update runtime @orderBy and @orderDirection
-    if @orderBy isnt column
-      @orderBy = column
-      @orderDirection = 'ASC'
+    if orderBy isnt column
+      orderBy = column
+      orderDirection = 'ASC'
     else
-      if @orderDirection is 'ASC'
-        @orderDirection = 'DESC'
+      if orderDirection is 'ASC'
+        orderDirection = 'DESC'
       else
-        @orderDirection = 'ASC'
+        orderDirection = 'ASC'
+
+    @orderBy = orderBy
+    @orderDirection = orderDirection
+    @customOrderBy = orderBy
+    @customOrderDirection = orderDirection
 
     # update store
-    @preferencesStore('order', 'orderBy', @orderBy)
-    @preferencesStore('order', 'orderDirection', @orderDirection)
-    @render()
+    @preferencesStore('order', 'customOrderBy', @orderBy)
+    @preferencesStore('order', 'customOrderDirection', @orderDirection)
+    render = =>
+      @renderTableFull()
+    App.QueueManager.add('tableRender', render)
+    App.QueueManager.run('tableRender')
 
   preferencesStore: (type, key, value) ->
     data = @preferencesGet()
@@ -601,3 +787,25 @@ class App.ControllerTable extends App.Controller
 
   preferencesStoreKey: =>
     "tablePrefs:#{@tableId}"
+
+  getBulkSelected: =>
+    ids = []
+    @$('[name="bulk"]:checked').each( (index, element) ->
+      id = $(element).val()
+      ids.push id
+    )
+    ids
+
+  setBulkSelected: (ids) ->
+    @$('[name="bulk"]').each( (index, element) ->
+      id = $(element).val()
+      for idSelected in ids
+        if idSelected is id
+          $(element).prop('checked', true)
+    )
+
+  _isSame: (array1, array2) ->
+    for position in [0..array1.length-1]
+      if array1[position] isnt array2[position]
+        return position
+    true

+ 45 - 33
app/assets/javascripts/app/controllers/ticket_overview.coffee

@@ -540,20 +540,23 @@ class App.TicketOverview extends App.Controller
   render: ->
     elLocal = $(App.view('ticket_overview/index')())
 
-    @navBarControllerVertical = new Navbar
+    @navBarControllerVertical = new Navbar(
       el:       elLocal.find('.overview-header')
       view:     @view
       vertical: true
+    )
 
-    @navBarController = new Navbar
+    @navBarController = new Navbar(
       el:   elLocal.filter('.sidebar')
       view: @view
+    )
 
-    @contentController = new Table
+    @contentController = new Table(
       el:          elLocal.find('.overview-table')
       view:        @view
       keyboardOn:  @keyboardOn
       keyboardOff: @keyboardOff
+    )
 
     @renderBatchOverlay(elLocal.filter('.js-batch-overlay'))
 
@@ -662,10 +665,12 @@ class App.TicketOverview extends App.Controller
     @viewLast = @view
 
     # build content
-    if @contentController
-      @contentController.update(
-        view: @view
-      )
+    @contentController = new Table(
+      el:          @$('.overview-table')
+      view:        @view
+      keyboardOn:  @keyboardOn
+      keyboardOff: @keyboardOff
+    )
 
   hide: =>
     @keyboardOff()
@@ -908,7 +913,7 @@ class Table extends App.Controller
     super
 
     if @view
-      @bindId = App.OverviewListCollection.bind(@view, @render)
+      @bindId = App.OverviewListCollection.bind(@view, @updateTable)
 
     # rerender view, e. g. on langauge change
     @bind 'ui:rerender', =>
@@ -924,15 +929,38 @@ class Table extends App.Controller
     for key, value of params
       @[key] = value
 
-    @view_mode = App.LocalStorage.get("mode:#{@view}", @Session.get('id')) || 's'
-    @log 'notice', 'view:', @view, @view_mode
-
     return if !@view
 
     if @view
       if @bindId
         App.OverviewListCollection.unbind(@bindId)
-      @bindId = App.OverviewListCollection.bind(@view, @render)
+      @bindId = App.OverviewListCollection.bind(@view, @updateTable)
+
+  updateTable: (data) =>
+    if !@table
+      @render(data)
+      return
+
+    # use cache
+    overview = data.overview
+    tickets  = data.tickets
+
+    return if !overview && !tickets
+
+    # get ticket list
+    ticketListShow = []
+    for ticket in tickets
+      ticketListShow.push App.Ticket.find(ticket.id)
+    console.log('overview', overview)
+    @overview = App.Overview.find(overview.id)
+    console.log('TTT', @overview.view.s)
+    @table.update(
+      overviewAttributes: @overview.view.s
+      objects:            ticketListShow
+      groupBy:            @overview.group_by
+      orderBy:            @overview.order.by
+      orderDirection:     @overview.order.direction
+    )
 
   render: (data) =>
     return if !data
@@ -943,6 +971,9 @@ class Table extends App.Controller
 
     return if !overview && !tickets
 
+    @view_mode = App.LocalStorage.get("mode:#{@view}", @Session.get('id')) || 's'
+    console.log 'notice', 'view:', @view, @view_mode
+
     # get ticket list
     ticketListShow = []
     for ticket in tickets
@@ -953,8 +984,6 @@ class Table extends App.Controller
       @html App.view('customer_not_ticket_exists')()
       return
 
-    @selected = @getSelected()
-
     # set page title
     @overview = App.Overview.find(overview.id)
 
@@ -1086,7 +1115,7 @@ class Table extends App.Controller
         attribute.title  = object.iconTitle()
         value
 
-      new App.ControllerTable(
+      @table = new App.ControllerTable(
         tableId:        "ticket_overview_#{@overview.id}"
         overview:       @overview.view.s
         el:             @$('.table-overview')
@@ -1123,8 +1152,6 @@ class Table extends App.Controller
             'click': callbackCheckbox
       )
 
-    @setSelected(@selected)
-
     # start user popups
     @userPopups()
 
@@ -1166,22 +1193,6 @@ class Table extends App.Controller
           bulkAll.prop('indeterminate', true)
     )
 
-  getSelected: ->
-    @ticketIDs = []
-    @$('.table-overview').find('[name="bulk"]:checked').each( (index, element) =>
-      ticketId = $(element).val()
-      @ticketIDs.push ticketId
-    )
-    @ticketIDs
-
-  setSelected: (ticketIDs) ->
-    @$('.table-overview').find('[name="bulk"]').each( (index, element) ->
-      ticketId = $(element).val()
-      for ticketIdSelected in ticketIDs
-        if ticketIdSelected is ticketId
-          $(element).prop('checked', true)
-    )
-
   viewmode: (e) =>
     e.preventDefault()
     @view_mode = $(e.target).data('mode')
@@ -1497,6 +1508,7 @@ class App.OverviewSettings extends App.ControllerModal
           App.OverviewListCollection.fetch(@overview.link)
         else
           App.OverviewIndexCollection.trigger()
+          console.log('TRIGGER', @overview.link)
           App.OverviewListCollection.trigger(@overview.link)
 
         # close modal

+ 2 - 4
app/assets/javascripts/app/views/generic/table.jst.eco

@@ -20,9 +20,7 @@
     <% for header, i in @headers: %>
       <th class="js-tableHead<%= " #{ header.className }" if header.className %><%= " align-#{ header.align }" if header.align %>" style="<% if header.displayWidth: %>width:<%= header.displayWidth %>px<% end %>" data-column-key="<%= header.name %>">
         <div class="table-column-head<%= ' js-sort' if @tableId %>">
-          <div class="table-column-title">
-          <%- @T(header.display) %>
-          </div>
+          <div class="table-column-title"><%- @T(header.display) %></div>
           <div class="table-column-sortIcon">
           <% if header.sortOrderIcon: %>
             <%- @Icon(header.sortOrderIcon[0], header.sortOrderIcon[1]) %>
@@ -36,5 +34,5 @@
     <% end %>
     </tr>
   </thead>
-  <tbody><%- @tableBody %></tbody>
+  <tbody class="js-tableBody"><%- @tableBody %></tbody>
 </table>

+ 1 - 1
app/assets/javascripts/app/views/generic/table_row.jst.eco

@@ -1,4 +1,4 @@
-<tr class="item<%= ' is-inactive' if @object.active is false %>" data-id="<%= @object.id %>" data-position="<%= @position %>">
+<tr class="item<%= ' is-inactive' if @object.active is false %>" data-id="<%= @object.id %>">
 <% if @sortable: %>
   <td class="table-draggable"><%- @Icon('draggable') %></td>
 <% end %>

+ 17 - 0
app/views/tests/table_extended.html.erb

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

+ 1 - 0
config/routes/test.rb

@@ -14,6 +14,7 @@ Zammad::Application.routes.draw do
   match '/tests_form_column_select',      to: 'tests#form_column_select',         via: :get
   match '/tests_form_searchable_select',  to: 'tests#form_searchable_select',     via: :get
   match '/tests_table',                   to: 'tests#table',                      via: :get
+  match '/tests_table_extended',          to: 'tests#table_extended',             via: :get
   match '/tests_html_utils',              to: 'tests#html_utils',                 via: :get
   match '/tests_ticket_selector',         to: 'tests#ticket_selector',            via: :get
   match '/tests_taskbar',                 to: 'tests#taskbar',                    via: :get

+ 180 - 180
public/assets/tests/table.js

@@ -1,5 +1,5 @@
 // form
-test( "table test", function() {
+test('table test', function() {
   App.i18n.set('de-de')
 
   $('#table').append('<hr><h1>table simple I</h1><div id="table1"></div>')
@@ -85,18 +85,18 @@ test( "table test", function() {
       }
     },
   })
-  equal( el.find('table > thead > tr').length, 1, 'row count')
-  equal( el.find('table > thead > tr > th:nth-child(1)').text().trim(), 'Name', 'check header')
-  equal( el.find('table > thead > tr > th:nth-child(2)').text().trim(), 'Erstellt', 'check header')
-  equal( el.find('table > thead > tr > th:nth-child(3)').text().trim(), 'Aktiv', 'check header')
-  equal( el.find('tbody > tr:nth-child(1) > td').length, 3, 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:first').text().trim(), '1 niedrig', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(3)').text().trim(), 'true', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(2) > td').length, 3, 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:first').text().trim(), '2 normal', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(3)').text().trim(), 'false', 'check row 2')
+  equal(el.find('table > thead > tr').length, 1, 'row count')
+  equal(el.find('table > thead > tr > th:nth-child(1)').text().trim(), 'Name', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(2)').text().trim(), 'Erstellt', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(3)').text().trim(), 'Aktiv', 'check header')
+  equal(el.find('tbody > tr:nth-child(1) > td').length, 3, 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:first').text().trim(), '1 niedrig', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(3)').text().trim(), 'true', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(2) > td').length, 3, 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:first').text().trim(), '2 normal', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(3)').text().trim(), 'false', 'check row 2')
 
   $('#table').append('<hr><h1>table simple II</h1><div id="table2"></div>')
   el = $('#table2')
@@ -108,18 +108,18 @@ test( "table test", function() {
     checkbox: false,
     radio:    false,
   })
-  equal( el.find('table > thead > tr').length, 1, 'row count')
-  equal( el.find('table > thead > tr > th:nth-child(1)').text().trim(), 'Name', 'check header')
-  equal( el.find('table > thead > tr > th:nth-child(2)').text().trim(), 'Erstellt', 'check header')
-  equal( el.find('table > thead > tr > th:nth-child(3)').text().trim(), 'Aktiv', 'check header')
-  equal( el.find('tbody > tr:nth-child(1) > td').length, 3, 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:first').text().trim(), '2 normal', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(3)').text().trim(), 'false', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(2) > td').length, 3, 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:first').text().trim(), '1 niedrig', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(3)').text().trim(), 'true', 'check row 2')
+  equal(el.find('table > thead > tr').length, 1, 'row count')
+  equal(el.find('table > thead > tr > th:nth-child(1)').text().trim(), 'Name', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(2)').text().trim(), 'Erstellt', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(3)').text().trim(), 'Aktiv', 'check header')
+  equal(el.find('tbody > tr:nth-child(1) > td').length, 3, 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:first').text().trim(), '2 normal', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(3)').text().trim(), 'false', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(2) > td').length, 3, 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:first').text().trim(), '1 niedrig', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(3)').text().trim(), 'true', 'check row 2')
 
   $('#table').append('<hr><h1>table simple III</h1><div id="table3"></div>')
   el = $('#table3')
@@ -130,16 +130,16 @@ test( "table test", function() {
     checkbox: false,
     radio:    false,
   })
-  equal( el.find('table > thead > tr').length, 1, 'row count')
-  equal( el.find('table > thead > tr > th:nth-child(1)').text().trim(), 'Name', 'check header')
+  equal(el.find('table > thead > tr').length, 1, 'row count')
+  equal(el.find('table > thead > tr > th:nth-child(1)').text().trim(), 'Name', 'check header')
   notEqual( el.find('table > thead > tr > th:nth-child(2)').text().trim(), 'Erstellt', 'check header')
   notEqual( el.find('table > thead > tr > th:nth-child(3)').text().trim(), 'Aktiv', 'check header')
-  equal( el.find('tbody > tr:nth-child(2) > td').length, 1, 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:first').text().trim(), '2 normal', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td').length, 1, 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:first').text().trim(), '2 normal', 'check row 2')
   notEqual( el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), '?', 'check row 2')
   notEqual( el.find('tbody > tr:nth-child(2) > td:nth-child(3)').text().trim(), 'true', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(1) > td').length, 1, 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:first').text().trim(), '1 niedrig', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td').length, 1, 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:first').text().trim(), '1 niedrig', 'check row 1')
   notEqual( el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), '?', 'check row 1')
   notEqual( el.find('tbody > tr:nth-child(1) > td:nth-child(3)').text().trim(), 'false', 'check row 1')
 
@@ -236,60 +236,60 @@ test( "table test", function() {
     objects:  App.Ticket.search({sortBy:'created_at', order: 'DESC'}),
     checkbox: true,
   })
-  equal( el.find('table > thead > tr').length, 1, 'row count')
-  equal( el.find('table > thead > tr > th:nth-child(1)').text().trim(), '', 'check header')
-  equal( el.find('table > thead > tr > th:nth-child(2)').text().trim(), '#', 'check header')
-  equal( el.find('table > thead > tr > th:nth-child(3)').text().trim(), 'Titel', 'check header')
-  equal( el.find('table > thead > tr > th:nth-child(4)').text().trim(), 'Besitzer', 'check header')
-  equal( el.find('table > thead > tr > th:nth-child(5)').text().trim(), 'Kunde', 'check header')
-  equal( el.find('table > thead > tr > th:nth-child(6)').text().trim(), 'Priorität', 'check header')
-  equal( el.find('table > thead > tr > th:nth-child(7)').text().trim(), 'Gruppe', 'check header')
-  equal( el.find('table > thead > tr > th:nth-child(8)').text().trim(), 'Status', 'check header')
-  equal( el.find('table > thead > tr > th:nth-child(9)').text().trim(), 'Erstellt am', 'check header')
-  equal( el.find('tbody > tr:nth-child(1) > td').length, 9, 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(1) input').val(), '3', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(1) input').prop('checked'), '', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(1)').text().trim(), '', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), '4713', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(3)').text().trim(), 'some title 3', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(4)').text().trim(), 'firstname56 lastname56', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(5)').text().trim(), '-', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(6)').text().trim(), '2 normal', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(7)').text().trim(), 'group 2', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(8)').text().trim(), 'neu', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(9)').text().trim(), '11.07.2014', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(2) > td').length, 9, 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(1) input').val(), '2', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(1) input').prop('checked'), '', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(1)').text().trim(), '', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), '4712', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(3)').text().trim(), 'some title 2', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(4)').text().trim(), '-', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(5)').text().trim(), '-', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(6)').text().trim(), '1 niedrig', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(7)').text().trim(), 'group 1', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(8)').text().trim(), 'offen', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(9)').text().trim(), '10.06.2014', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(3) > td').length, 9, 'check row 3')
-  equal( el.find('tbody > tr:nth-child(3) > td:nth-child(1) input').val(), '1', 'check row 3')
-  equal( el.find('tbody > tr:nth-child(3) > td:nth-child(1) input').prop('checked'), '', 'check row 3')
-  equal( el.find('tbody > tr:nth-child(3) > td:nth-child(1)').text().trim(), '', 'check row 3')
-  equal( el.find('tbody > tr:nth-child(3) > td:nth-child(2)').text().trim(), '4711', 'check row 3')
-  equal( el.find('tbody > tr:nth-child(3) > td:nth-child(3)').text().trim(), 'some title 1', 'check row 3')
-  equal( el.find('tbody > tr:nth-child(3) > td:nth-child(4)').text().trim(), 'firstname55 lastname55', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(3) > td:nth-child(5)').text().trim(), 'firstname56 lastname56', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(3) > td:nth-child(6)').text().trim(), '1 niedrig', 'check row 3')
-  equal( el.find('tbody > tr:nth-child(3) > td:nth-child(7)').text().trim(), 'group 2', 'check row 3')
-  equal( el.find('tbody > tr:nth-child(3) > td:nth-child(8)').text().trim(), 'neu', 'check row 3')
-  equal( el.find('tbody > tr:nth-child(3) > td:nth-child(9)').text().trim(), '10.06.2014', 'check row 3')
+  equal(el.find('table > thead > tr').length, 1, 'row count')
+  equal(el.find('table > thead > tr > th:nth-child(1)').text().trim(), '', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(2)').text().trim(), '#', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(3)').text().trim(), 'Titel', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(4)').text().trim(), 'Besitzer', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(5)').text().trim(), 'Kunde', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(6)').text().trim(), 'Priorität', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(7)').text().trim(), 'Gruppe', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(8)').text().trim(), 'Status', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(9)').text().trim(), 'Erstellt am', 'check header')
+  equal(el.find('tbody > tr:nth-child(1) > td').length, 9, 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(1) input').val(), '3', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(1) input').prop('checked'), '', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(1)').text().trim(), '', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), '4713', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(3)').text().trim(), 'some title 3', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(4)').text().trim(), 'firstname56 lastname56', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(5)').text().trim(), '-', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(6)').text().trim(), '2 normal', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(7)').text().trim(), 'group 2', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(8)').text().trim(), 'neu', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(9)').text().trim(), '11.07.2014', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(2) > td').length, 9, 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(1) input').val(), '2', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(1) input').prop('checked'), '', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(1)').text().trim(), '', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), '4712', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(3)').text().trim(), 'some title 2', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(4)').text().trim(), '-', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(5)').text().trim(), '-', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(6)').text().trim(), '1 niedrig', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(7)').text().trim(), 'group 1', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(8)').text().trim(), 'offen', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(9)').text().trim(), '10.06.2014', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(3) > td').length, 9, 'check row 3')
+  equal(el.find('tbody > tr:nth-child(3) > td:nth-child(1) input').val(), '1', 'check row 3')
+  equal(el.find('tbody > tr:nth-child(3) > td:nth-child(1) input').prop('checked'), '', 'check row 3')
+  equal(el.find('tbody > tr:nth-child(3) > td:nth-child(1)').text().trim(), '', 'check row 3')
+  equal(el.find('tbody > tr:nth-child(3) > td:nth-child(2)').text().trim(), '4711', 'check row 3')
+  equal(el.find('tbody > tr:nth-child(3) > td:nth-child(3)').text().trim(), 'some title 1', 'check row 3')
+  equal(el.find('tbody > tr:nth-child(3) > td:nth-child(4)').text().trim(), 'firstname55 lastname55', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(3) > td:nth-child(5)').text().trim(), 'firstname56 lastname56', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(3) > td:nth-child(6)').text().trim(), '1 niedrig', 'check row 3')
+  equal(el.find('tbody > tr:nth-child(3) > td:nth-child(7)').text().trim(), 'group 2', 'check row 3')
+  equal(el.find('tbody > tr:nth-child(3) > td:nth-child(8)').text().trim(), 'neu', 'check row 3')
+  equal(el.find('tbody > tr:nth-child(3) > td:nth-child(9)').text().trim(), '10.06.2014', 'check row 3')
 
   el.find('input[name="bulk_all"]').click()
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(1) input').prop('checked'), true, 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(1) input').val(), '3', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(1) input').prop('checked'), true, 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(1) input').val(), '2', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(3) > td:nth-child(1) input').prop('checked'), true, 'check row 3')
-  equal( el.find('tbody > tr:nth-child(3) > td:nth-child(1) input').val(), '1', 'check row 3')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(1) input').prop('checked'), true, 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(1) input').val(), '3', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(1) input').prop('checked'), true, 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(1) input').val(), '2', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(3) > td:nth-child(1) input').prop('checked'), true, 'check row 3')
+  equal(el.find('tbody > tr:nth-child(3) > td:nth-child(1) input').val(), '1', 'check row 3')
 
   $('#table').append('<hr><h1>table complex II</h1><div id="table5"></div>')
   el = $('#table5')
@@ -309,55 +309,55 @@ test( "table test", function() {
       }
     },
   })
-  equal( el.find('table > thead > tr').length, 1, 'row count')
-  equal( el.find('table > thead > tr > th:nth-child(1)').text().trim(), '', 'check header')
-  equal( el.find('table > thead > tr > th:nth-child(2)').text().trim(), '#', 'check header')
-  equal( el.find('table > thead > tr > th:nth-child(3)').text().trim(), 'Titel', 'check header')
-  equal( el.find('table > thead > tr > th:nth-child(4)').text().trim(), 'Besitzer', 'check header')
-  equal( el.find('table > thead > tr > th:nth-child(5)').text().trim(), 'Kunde', 'check header')
-  equal( el.find('table > thead > tr > th:nth-child(6)').text().trim(), 'Priorität', 'check header')
-  equal( el.find('table > thead > tr > th:nth-child(7)').text().trim(), 'Status', 'check header')
-  equal( el.find('table > thead > tr > th:nth-child(8)').text().trim(), 'Erstellt am', 'check header')
-  equal( el.find('tbody > tr:nth-child(1) > td').length, 1, 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(1)').text().trim(), 'group 1', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(2) > td').length, 8, 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(1) input').val(), '2', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(1) input').prop('checked'), '', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(1)').text().trim(), '', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), '4712', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(3)').text().trim(), 'some title 2', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(4)').text().trim(), '-', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(5)').text().trim(), '-', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(6)').text().trim(), '1 niedrig', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(7)').text().trim(), 'offen', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(8)').text().trim(), '10.06.2014', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(3) > td').length, 1, 'check row 3')
-  equal( el.find('tbody > tr:nth-child(3) > td:nth-child(1)').text().trim(), 'group 2', 'check row 4')
-  equal( el.find('tbody > tr:nth-child(4) > td').length, 8, 'check row 4')
-  equal( el.find('tbody > tr:nth-child(4) > td:nth-child(1) input').val(), '3', 'check row 4')
-  equal( el.find('tbody > tr:nth-child(4) > td:nth-child(1) input').prop('checked'), '', 'check row 4')
-  equal( el.find('tbody > tr:nth-child(4) > td:nth-child(1)').text().trim(), '', 'check row 4')
-  equal( el.find('tbody > tr:nth-child(4) > td:nth-child(2)').text().trim(), '4713', 'check row 4')
-  equal( el.find('tbody > tr:nth-child(4) > td:nth-child(3)').text().trim(), 'some title 3', 'check row 4')
-  equal( el.find('tbody > tr:nth-child(4) > td:nth-child(4)').text().trim(), 'firstname56 lastname56', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(4) > td:nth-child(5)').text().trim(), '-', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(4) > td:nth-child(6)').text().trim(), '2 normal', 'check row 4')
-  equal( el.find('tbody > tr:nth-child(4) > td:nth-child(7)').text().trim(), 'neu', 'check row 4')
-  equal( el.find('tbody > tr:nth-child(4) > td:nth-child(8)').text().trim(), '11.07.2014', 'check row 4')
+  equal(el.find('table > thead > tr').length, 1, 'row count')
+  equal(el.find('table > thead > tr > th:nth-child(1)').text().trim(), '', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(2)').text().trim(), '#', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(3)').text().trim(), 'Titel', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(4)').text().trim(), 'Besitzer', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(5)').text().trim(), 'Kunde', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(6)').text().trim(), 'Priorität', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(7)').text().trim(), 'Status', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(8)').text().trim(), 'Erstellt am', 'check header')
+  equal(el.find('tbody > tr:nth-child(1) > td').length, 1, 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(1)').text().trim(), 'group 1', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(2) > td').length, 8, 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(1) input').val(), '2', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(1) input').prop('checked'), '', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(1)').text().trim(), '', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), '4712', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(3)').text().trim(), 'some title 2', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(4)').text().trim(), '-', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(5)').text().trim(), '-', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(6)').text().trim(), '1 niedrig', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(7)').text().trim(), 'offen', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(8)').text().trim(), '10.06.2014', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(3) > td').length, 1, 'check row 3')
+  equal(el.find('tbody > tr:nth-child(3) > td:nth-child(1)').text().trim(), 'group 2', 'check row 4')
+  equal(el.find('tbody > tr:nth-child(4) > td').length, 8, 'check row 4')
+  equal(el.find('tbody > tr:nth-child(4) > td:nth-child(1) input').val(), '3', 'check row 4')
+  equal(el.find('tbody > tr:nth-child(4) > td:nth-child(1) input').prop('checked'), '', 'check row 4')
+  equal(el.find('tbody > tr:nth-child(4) > td:nth-child(1)').text().trim(), '', 'check row 4')
+  equal(el.find('tbody > tr:nth-child(4) > td:nth-child(2)').text().trim(), '4713', 'check row 4')
+  equal(el.find('tbody > tr:nth-child(4) > td:nth-child(3)').text().trim(), 'some title 3', 'check row 4')
+  equal(el.find('tbody > tr:nth-child(4) > td:nth-child(4)').text().trim(), 'firstname56 lastname56', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(4) > td:nth-child(5)').text().trim(), '-', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(4) > td:nth-child(6)').text().trim(), '2 normal', 'check row 4')
+  equal(el.find('tbody > tr:nth-child(4) > td:nth-child(7)').text().trim(), 'neu', 'check row 4')
+  equal(el.find('tbody > tr:nth-child(4) > td:nth-child(8)').text().trim(), '11.07.2014', 'check row 4')
 
   el.find('input[name="bulk"]:eq(1)').click()
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(1) input').prop('checked'), '', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(1) input').val(), '2', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(4) > td:nth-child(1) input').prop('checked'), true, 'check row 4')
-  equal( el.find('tbody > tr:nth-child(4) > td:nth-child(1) input').val(), '3', 'check row 4')
-  equal( el.find('tbody > tr:nth-child(5) > td:nth-child(1) input').prop('checked'), '', 'check row 5')
-  equal( el.find('tbody > tr:nth-child(5) > td:nth-child(1) input').val(), '1', 'check row 5')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(1) input').prop('checked'), '', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(1) input').val(), '2', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(4) > td:nth-child(1) input').prop('checked'), true, 'check row 4')
+  equal(el.find('tbody > tr:nth-child(4) > td:nth-child(1) input').val(), '3', 'check row 4')
+  equal(el.find('tbody > tr:nth-child(5) > td:nth-child(1) input').prop('checked'), '', 'check row 5')
+  equal(el.find('tbody > tr:nth-child(5) > td:nth-child(1) input').val(), '1', 'check row 5')
   el.find('tbody > tr:nth-child(5) > td:nth-child(1) label').click()
-  equal( el.find('tbody > tr:nth-child(5) > td:nth-child(1) input').prop('checked'), true, 'check row 5')
-  equal( el.find('tbody > tr:nth-child(5) > td:nth-child(1) input').val(), '1', 'check row 5')
+  equal(el.find('tbody > tr:nth-child(5) > td:nth-child(1) input').prop('checked'), true, 'check row 5')
+  equal(el.find('tbody > tr:nth-child(5) > td:nth-child(1) input').val(), '1', 'check row 5')
 });
 
-test( "table test 2", function() {
+test('table test 2', function() {
   App.i18n.set('de-de')
 
   $('#table').append('<hr><h1>table with hash</h1><div id="table-hash1"></div>')
@@ -413,27 +413,27 @@ test( "table test 2", function() {
     model:    App.Channel,
     objects:  App.Channel.search({sortBy:'adapter', order: 'ASC'}),
   })
-  equal( el.find('table > thead > tr').length, 1, 'row count')
-  equal( el.find('table > thead > tr > th:nth-child(1)').text().trim(), 'Typ', 'check header')
-  equal( el.find('table > thead > tr > th:nth-child(2)').text().trim(), 'Host', 'check header')
-  equal( el.find('table > thead > tr > th:nth-child(3)').text().trim(), 'Benutzer', 'check header')
-  equal( el.find('table > thead > tr > th:nth-child(4)').text().trim(), 'Aktiv', 'check header')
-  equal( el.find('table > thead > tr > th:nth-child(5)').text().trim(), 'Löschen', 'check header')
-  equal( el.find('tbody > tr:nth-child(1) > td').length, 5, 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(1)').text().trim(), 'adapter1', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), 'host1', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(3)').text().trim(), 'user1', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(4)').text().trim(), 'ja', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(5)').text().trim(), '', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(2) > td').length, 5, 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(1)').text().trim(), 'adapter2', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), 'host2', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(3)').text().trim(), 'user2', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(4)').text().trim(), 'ja', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(5)').text().trim(), '', 'check row 2')
+  equal(el.find('table > thead > tr').length, 1, 'row count')
+  equal(el.find('table > thead > tr > th:nth-child(1)').text().trim(), 'Typ', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(2)').text().trim(), 'Host', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(3)').text().trim(), 'Benutzer', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(4)').text().trim(), 'Aktiv', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(5)').text().trim(), 'Löschen', 'check header')
+  equal(el.find('tbody > tr:nth-child(1) > td').length, 5, 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(1)').text().trim(), 'adapter1', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), 'host1', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(3)').text().trim(), 'user1', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(4)').text().trim(), 'ja', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(5)').text().trim(), '', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(2) > td').length, 5, 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(1)').text().trim(), 'adapter2', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), 'host2', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(3)').text().trim(), 'user2', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(4)').text().trim(), 'ja', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(5)').text().trim(), '', 'check row 2')
 });
 
-test( "table test 3", function() {
+test('table test 3', function() {
   App.i18n.set('de-de')
 
   $('#table').append('<hr><h1>table with link</h1><div id="table-link1"></div>')
@@ -508,31 +508,31 @@ test( "table test 3", function() {
       },
     },
   })
-  equal( el.find('table > thead > tr').length, 1, 'row count')
-  equal( el.find('table > thead > tr > th:nth-child(1)').text().trim(), 'richtiger Name', 'check header')
-  equal( el.find('table > thead > tr > th:nth-child(3)').text().trim(), 'Some Name', 'check header')
-  equal( el.find('tbody > tr:nth-child(1) > td').length, 3, 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(1)').text().trim(), 'realname 55', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), 'email 55', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(3)').text().trim(), '', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(3) > a > span').hasClass('glyphicon-user'), true, 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(3) > a > span').hasClass('glyphicon'), true, 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(3)').attr('title'), 'Umschalten zu', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(3) > a > span').data('some'), 'value55', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(3) > a > span').data('xxx'), '55', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td').length, 3, 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(1)').text().trim(), 'realname 56', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), 'email 56', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(3)').text().trim(), '', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(3) > a > span').hasClass('glyphicon-user'), true, 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(3) > a > span').hasClass('glyphicon'), true, 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(3)').attr('title'), 'Umschalten zu', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(3) > a > span').data('some'), 'value56', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(3) > a > span').data('xxx'), '56', 'check row 2')
+  equal(el.find('table > thead > tr').length, 1, 'row count')
+  equal(el.find('table > thead > tr > th:nth-child(1)').text().trim(), 'richtiger Name', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(3)').text().trim(), 'Some Name', 'check header')
+  equal(el.find('tbody > tr:nth-child(1) > td').length, 3, 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(1)').text().trim(), 'realname 55', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), 'email 55', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(3)').text().trim(), '', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(3) > a > span').hasClass('glyphicon-user'), true, 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(3) > a > span').hasClass('glyphicon'), true, 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(3)').attr('title'), 'Umschalten zu', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(3) > a > span').data('some'), 'value55', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(3) > a > span').data('xxx'), '55', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td').length, 3, 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(1)').text().trim(), 'realname 56', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), 'email 56', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(3)').text().trim(), '', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(3) > a > span').hasClass('glyphicon-user'), true, 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(3) > a > span').hasClass('glyphicon'), true, 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(3)').attr('title'), 'Umschalten zu', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(3) > a > span').data('some'), 'value56', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(3) > a > span').data('xxx'), '56', 'check row 2')
 
 });
 
-test( "table test 4", function() {
+test('table test 4', function() {
   App.i18n.set('de-de')
 
   $('#table').append('<hr><h1>table with data</h1><div id="table-data1"></div>')
@@ -554,16 +554,16 @@ test( "table test 4", function() {
     objects: data
   });
 
-  equal( el.find('table > thead > tr').length, 1, 'row count')
-  equal( el.find('table > thead > tr > th:nth-child(1)').text().trim(), 'Name', 'check header')
-  equal( el.find('table > thead > tr > th:nth-child(2)').text().trim(), 'Data', 'check header')
-  equal( el.find('table > thead > tr > th:nth-child(3)').text().trim(), 'Aktiv', 'check header')
-  equal( el.find('tbody > tr:nth-child(1) > td').length, 3, 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:first').text().trim(), 'some name 1', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), 'some data 1', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(1) > td:nth-child(3)').text().trim(), 'true', 'check row 1')
-  equal( el.find('tbody > tr:nth-child(2) > td').length, 3, 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:first').text().trim(), 'some name 2', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), 'some data 2', 'check row 2')
-  equal( el.find('tbody > tr:nth-child(2) > td:nth-child(3)').text().trim(), 'false', 'check row 2')
+  equal(el.find('table > thead > tr').length, 1, 'row count')
+  equal(el.find('table > thead > tr > th:nth-child(1)').text().trim(), 'Name', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(2)').text().trim(), 'Data', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(3)').text().trim(), 'Aktiv', 'check header')
+  equal(el.find('tbody > tr:nth-child(1) > td').length, 3, 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:first').text().trim(), 'some name 1', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), 'some data 1', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(3)').text().trim(), 'true', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(2) > td').length, 3, 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:first').text().trim(), 'some name 2', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), 'some data 2', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(3)').text().trim(), 'false', 'check row 2')
 });

+ 274 - 0
public/assets/tests/table_extended.js

@@ -0,0 +1,274 @@
+// initial list
+test('table new - initial list', function() {
+  App.i18n.set('de-de')
+
+  $('#table').append('<hr><h1>table with data</h1><div id="table-new1"></div>')
+  var el = $('#table-new1')
+
+  App.TicketPriority.refresh([
+    {
+      id:         1,
+      name:       '1 low',
+      note:       'some note 1',
+      active:     true,
+      created_at: '2014-06-10T11:17:34.000Z',
+    },
+    {
+      id:         2,
+      name:       '2 normal',
+      note:       'some note 2',
+      active:     false,
+      created_at: '2014-06-10T10:17:34.000Z',
+    },
+  ], {clear: true})
+
+  var table = new App.ControllerTable({
+    el:                 el,
+    overviewAttributes: ['name', 'created_at', 'active'],
+    model:              App.TicketPriority,
+    objects:            App.TicketPriority.search({sortBy:'name', order: 'ASC'}),
+    checkbox:           false,
+    radio:              false,
+  })
+  //equal(el.find('table').length, 0, 'row count')
+  //table.render()
+  equal(el.find('table > thead > tr').length, 1, 'row count')
+  equal(el.find('table > thead > tr > th:nth-child(1)').text().trim(), 'Name', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(2)').text().trim(), 'Erstellt', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(3)').text().trim(), 'Aktiv', 'check header')
+  equal(el.find('tbody > tr:nth-child(1) > td').length, 3, 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:first').text().trim(), '1 niedrig', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(3)').text().trim(), 'true', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(2) > td').length, 3, 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:first').text().trim(), '2 normal', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(3) > td').length, 0, 'check row 3')
+
+  result = table.update({sync: true, objects: App.TicketPriority.search({sortBy:'name', order: 'ASC'})})
+  equal(result[0], 'noChanges')
+
+  equal(el.find('table > thead > tr').length, 1, 'row count')
+  equal(el.find('table > thead > tr > th:nth-child(1)').text().trim(), 'Name', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(2)').text().trim(), 'Erstellt', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(3)').text().trim(), 'Aktiv', 'check header')
+  equal(el.find('tbody > tr:nth-child(1) > td').length, 3, 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:first').text().trim(), '1 niedrig', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(3)').text().trim(), 'true', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(2) > td').length, 3, 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:first').text().trim(), '2 normal', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 2')
+
+  App.TicketPriority.refresh([
+    {
+      id:         1,
+      name:       'Priority',
+      note:       'some note 1',
+      active:     true,
+      created_at: '2014-06-10T11:17:34.000Z',
+    },
+  ], {clear: true})
+
+  result = table.update({sync: true, objects: App.TicketPriority.search({sortBy:'name', order: 'ASC'})})
+  equal(result[0], 'fullRender.lenghtChanged')
+  equal(result[1], 2)
+  equal(result[2], 1)
+
+  equal(el.find('table > thead > tr').length, 1, 'row count')
+  equal(el.find('table > thead > tr > th:nth-child(1)').text().trim(), 'Name', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(2)').text().trim(), 'Erstellt', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(3)').text().trim(), 'Aktiv', 'check header')
+  equal(el.find('tbody > tr:nth-child(1) > td').length, 3, 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:first').text().trim(), 'Priorität', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(3)').text().trim(), 'true', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(2) > td').length, 0, 'check row 2')
+
+  App.TicketPriority.refresh([], {clear: true})
+
+  result = table.update({sync: true, objects: App.TicketPriority.search({sortBy:'name', order: 'ASC'})})
+  equal(result[0], 'emptyList')
+
+  equal(el.find('table > thead > tr').length, 1, 'row count')
+  equal(el.find('table > thead > tr > th:nth-child(1)').text().trim(), 'Keine Einträge', 'check header')
+  equal(el.find('tbody > tr:nth-child(1) > td').length, 0, 'check row 1')
+
+  App.TicketPriority.refresh([
+    {
+      id:         1,
+      name:       '1 low',
+      note:       'some note 1',
+      active:     true,
+      created_at: '2014-06-10T11:17:34.000Z',
+    },
+    {
+      id:         2,
+      name:       '2 normal',
+      note:       'some note 2',
+      active:     false,
+      created_at: '2014-06-10T10:17:34.000Z',
+    },
+  ], {clear: true})
+
+  result = table.update({sync: true, objects: App.TicketPriority.search({sortBy:'name', order: 'ASC'})})
+  equal(result[0], 'fullRender')
+
+  equal(el.find('table > thead > tr').length, 1, 'row count')
+  equal(el.find('table > thead > tr > th:nth-child(1)').text().trim(), 'Name', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(2)').text().trim(), 'Erstellt', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(3)').text().trim(), 'Aktiv', 'check header')
+  equal(el.find('tbody > tr:nth-child(1) > td').length, 3, 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:first').text().trim(), '1 niedrig', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(3)').text().trim(), 'true', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(2) > td').length, 3, 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:first').text().trim(), '2 normal', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(3) > td').length, 0, 'check row 3')
+
+  App.TicketPriority.refresh([
+    {
+      id:         1,
+      name:       '1 low',
+      note:       'some note 1',
+      active:     true,
+      created_at: '2014-06-10T11:17:34.000Z',
+    },
+    {
+      id:         2,
+      name:       '2 normal',
+      note:       'some note 2',
+      active:     false,
+      created_at: '2014-06-10T10:17:34.000Z',
+    },
+    {
+      id:         3,
+      name:       '3 high',
+      note:       'some note 3',
+      active:     false,
+      created_at: '2014-06-10T10:17:38.000Z',
+    },
+  ], {clear: true})
+
+  result = table.update({sync: true, objects: App.TicketPriority.search({sortBy:'name', order: 'ASC'})})
+  equal(result[0], 'fullRender.lenghtChanged')
+  equal(result[1], 2)
+  equal(result[2], 3)
+
+  equal(el.find('table > thead > tr').length, 1, 'row count')
+  equal(el.find('table > thead > tr > th:nth-child(1)').text().trim(), 'Name', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(2)').text().trim(), 'Erstellt', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(3)').text().trim(), 'Aktiv', 'check header')
+  equal(el.find('tbody > tr:nth-child(1) > td').length, 3, 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:first').text().trim(), '1 niedrig', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(3)').text().trim(), 'true', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(2) > td').length, 3, 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:first').text().trim(), '2 normal', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(3) > td').length, 3, 'check row 3')
+  equal(el.find('tbody > tr:nth-child(3) > td:first').text().trim(), '3 hoch', 'check row 3')
+  equal(el.find('tbody > tr:nth-child(3) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 3')
+  equal(el.find('tbody > tr:nth-child(4) > td').length, 0, 'check row 4')
+
+  result = table.update({sync: true, orderDirection: 'DESC', orderBy: 'name'})
+  equal(result[0], 'fullRender.contentChanged')
+  equal(result[1], 0)
+
+  equal(el.find('table > thead > tr').length, 1, 'row count')
+  equal(el.find('table > thead > tr > th:nth-child(1)').text().trim(), 'Name', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(2)').text().trim(), 'Erstellt', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(3)').text().trim(), 'Aktiv', 'check header')
+  equal(el.find('tbody > tr:nth-child(1) > td').length, 3, 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:first').text().trim(), '3 hoch', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(2) > td').length, 3, 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:first').text().trim(), '2 normal', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(3) > td').length, 3, 'check row 3')
+  equal(el.find('tbody > tr:nth-child(3) > td:first').text().trim(), '1 niedrig', 'check row 3')
+  equal(el.find('tbody > tr:nth-child(3) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 3')
+  equal(el.find('tbody > tr:nth-child(3) > td:nth-child(3)').text().trim(), 'true', 'check row 3')
+  equal(el.find('tbody > tr:nth-child(4) > td').length, 0, 'check row 4')
+
+  result = table.update({sync: true, orderDirection: 'ASC', orderBy: 'name'})
+  equal(result[0], 'fullRender.contentChanged')
+  equal(result[1], 0)
+
+  equal(el.find('table > thead > tr').length, 1, 'row count')
+  equal(el.find('table > thead > tr > th:nth-child(1)').text().trim(), 'Name', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(2)').text().trim(), 'Erstellt', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(3)').text().trim(), 'Aktiv', 'check header')
+  equal(el.find('tbody > tr:nth-child(1) > td').length, 3, 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:first').text().trim(), '1 niedrig', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(3)').text().trim(), 'true', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(2) > td').length, 3, 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:first').text().trim(), '2 normal', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 2')
+  equal(el.find('tbody > tr:nth-child(3) > td').length, 3, 'check row 3')
+  equal(el.find('tbody > tr:nth-child(3) > td:first').text().trim(), '3 hoch', 'check row 3')
+  equal(el.find('tbody > tr:nth-child(3) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 3')
+  equal(el.find('tbody > tr:nth-child(4) > td').length, 0, 'check row 4')
+
+  App.TicketPriority.refresh([
+    {
+      id:         1,
+      name:       '1 low',
+      note:       'some note 1',
+      active:     true,
+      created_at: '2014-06-10T11:17:34.000Z',
+    },
+    {
+      id:         3,
+      name:       '3 high',
+      note:       'some note 3',
+      active:     false,
+      created_at: '2014-06-10T10:17:38.000Z',
+    },
+  ], {clear: true})
+
+  result = table.update({sync: true, objects: App.TicketPriority.search({sortBy:'name', order: 'ASC'})})
+  equal(result[0], 'fullRender.contentRemoved')
+  equal(result[1], 1)
+  notOk(result[2])
+
+  equal(el.find('table > thead > tr').length, 1, 'row count')
+  equal(el.find('table > thead > tr > th:nth-child(1)').text().trim(), 'Name', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(2)').text().trim(), 'Erstellt', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(3)').text().trim(), 'Aktiv', 'check header')
+  equal(el.find('tbody > tr:nth-child(1) > td').length, 3, 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:first').text().trim(), '1 niedrig', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(3)').text().trim(), 'true', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(2) > td').length, 3, 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:first').text().trim(), '3 hoch', 'check row 3')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 3')
+  equal(el.find('tbody > tr:nth-child(3) > td').length, 0, 'check row 3')
+
+  result = table.update({sync: true, overviewAttributes: ['name', 'created_at']})
+  equal(result[0], 'fullRender.overviewAttributesChanged')
+
+  equal(el.find('table > thead > tr').length, 1, 'row count')
+  equal(el.find('table > thead > tr > th:nth-child(1)').text().trim(), 'Name', 'check header')
+  equal(el.find('table > thead > tr > th:nth-child(2)').text().trim(), 'Erstellt', 'check header')
+  equal(el.find('table > thead > tr > th').length, 2, 'check header')
+  equal(el.find('tbody > tr:nth-child(1) > td').length, 2, 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:first').text().trim(), '1 niedrig', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 1')
+  equal(el.find('tbody > tr:nth-child(2) > td').length, 2, 'check row 2')
+  equal(el.find('tbody > tr:nth-child(2) > td:first').text().trim(), '3 hoch', 'check row 3')
+  equal(el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), '10.06.2014', 'check row 3')
+  equal(el.find('tbody > tr:nth-child(3) > td').length, 0, 'check row 3')
+
+  App.TicketPriority.refresh([], {clear: true})
+
+  result = table.update({sync: true, objects: App.TicketPriority.search({sortBy:'name', order: 'ASC'})})
+  equal(result[0], 'emptyList')
+
+  equal(el.find('table > thead > tr').length, 1, 'row count')
+  equal(el.find('table > thead > tr > th:nth-child(1)').text().trim(), 'Keine Einträge', 'check header')
+  equal(el.find('tbody > tr:nth-child(1) > td').length, 0, 'check row 1')
+
+})

+ 4 - 0
public/assets/tests/ui.js

@@ -109,4 +109,8 @@ test("check pretty date", function() {
   result = App.PrettyDate.humanTime(current.getTime() + (60050 * 60 * 24 * 30.5));
   equal(result, 'in 30 days', 'in 30.5 days')
 
+  // 
+
+
+
 });

+ 7 - 0
test/browser/aab_unit_test.rb

@@ -118,6 +118,13 @@ class AAbUnitTest < TestCase
       value: '0',
     )
 
+    location(url: browser_url + '/tests_table_extended')
+    sleep 4
+    match(
+      css: '.result .failed',
+      value: '0',
+    )
+
     location(url: browser_url + '/tests_html_utils')
     sleep 4
     match(