Improved collection handling.

This commit is contained in:
Martin Edenhofer 2012-09-25 00:50:41 +02:00
parent 8bbe1c9289
commit 422ae18a2d
25 changed files with 1477 additions and 1061 deletions

View file

@ -95,8 +95,8 @@ class App.Controller extends Spine.Controller
# if no content exists, try firstname/lastname
if !object[row.name]['name']
if object[row.name]['firstname'] || object[row.name]['lastname']
object[row.name]['name'] = (object[row.name]['firstname'] || '') + ' ' + (object[row.name]['lastname'] || '')
if object[row.name]['realname']
object[row.name]['name'] = object[row.name]['realname']
# if it isnt a object, create one
else if typeof object[row.name] isnt 'object'
@ -112,7 +112,7 @@ class App.Controller extends Spine.Controller
# execute callback on content
if row.callback
object[row.name]['name'] = row.callback(object[row.name]['name'])
object[row.name]['name'] = row.callback( object[row.name]['name'] )
# lookup relation
if !object[row.name]['name']
@ -120,7 +120,7 @@ class App.Controller extends Spine.Controller
for attribute in attributes
if rowWithoutId is attribute.name
if attribute.relation && App[attribute.relation]
record = App[attribute.relation].find( object[rowWithoutId] )
record = App.Collection.find( attribute.relation, object[rowWithoutId] )
object[row.name]['name'] = record.name
# @log 'table', 'header', header, 'overview', data_types, 'objects', objects
@ -158,12 +158,12 @@ class App.Controller extends Spine.Controller
{ name: 'ticket_priority', translate: true },
{ name: 'group' },
{ name: 'owner', class: 'user-data', data: { id: true } },
{ name: 'created_at', callback: @humanTime },
{ name: 'last_contact', callback: @humanTime },
{ name: 'last_contact_agent', callback: @humanTime },
{ name: 'last_contact_customer', callback: @humanTime },
{ name: 'first_response', callback: @humanTime },
{ name: 'close_time', callback: @humanTime },
{ 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 },
]
shown_all_attributes = []
for all_attribute in all_attributes
@ -190,25 +190,38 @@ class App.Controller extends Spine.Controller
humanTime: (time) =>
current = new Date()
created = new Date(time)
diff = (current - created) / 1000
string = ''
diff = ( current - created ) / 1000
if diff >= 86400
unit = Math.round( (diff / 86400) )
if unit > 1
return unit + ' ' + T('days')
else
return unit + ' ' + T('day')
unit = Math.round( ( diff / 86400 ) )
# if unit > 1
# return unit + ' ' + T('days')
# else
# return unit + ' ' + T('day')
string = unit + ' ' + T('d')
if diff >= 3600
unit = Math.round( (diff / 3600) )
if unit > 1
return unit + ' ' + T('hours')
unit = Math.round( ( diff / 3600 ) % 24 )
# if unit > 1
# return unit + ' ' + T('hours')
# else
# return unit + ' ' + T('hour')
if string isnt ''
string = string + ' ' + unit + ' ' + T('h')
return string
else
return unit + ' ' + T('hour')
if diff <= 3600
unit = Math.round( (diff / 60) )
if unit > 1
return unit + ' ' + T('minutes')
string = unit + ' ' + T('h')
if diff <= 86400
unit = Math.round( ( diff / 60 ) % 60 )
# if unit > 1
# return unit + ' ' + T('minutes')
# else
# return unit + ' ' + T('minute')
if string isnt ''
string = string + ' ' + unit + ' ' + T('m')
return string
else
return unit + ' ' + T('minute')
string = unit + ' ' + T('m')
return string
userInfo: (data) =>
# start customer info controller
@ -230,6 +243,21 @@ class App.Controller extends Spine.Controller
@navigate '#login'
return false
frontendTime: (timestamp) ->
'<span class="humanTimeFromNow" data-time="' + timestamp + '">?</span>'
frontendTimeUpdate: ->
update = =>
ui = @
$('.humanTimeFromNow').each( ->
# console.log('rewrite frontendTimeUpdate', this)
timestamp = $(this).data('time')
time = ui.humanTime( timestamp )
$(this).attr( 'title', Ts(timestamp) )
$(this).text( time )
)
@interval( update, 30000, 'frontendTimeUpdate' )
clearInterval: (interval_id) =>
# check global var
if !@intervalID
@ -237,13 +265,13 @@ class App.Controller extends Spine.Controller
clearInterval( @intervalID[interval_id] ) if @intervalID[interval_id]
interval: (action, interval, interval_id) =>
interval: (callback, interval, interval_id) =>
# check global var
if !@intervalID
@intervalID = {}
action()
callback()
# auto save
every = (ms, cb) -> setInterval cb, ms
@ -253,7 +281,7 @@ class App.Controller extends Spine.Controller
# request new data
@intervalID[interval_id] = every interval, () =>
action()
callback()
userPopups: (position = 'right') ->
@ -267,11 +295,11 @@ class App.Controller extends Spine.Controller
placement: position,
title: ->
user_id = $(@).data('id')
user = App.User.find(user_id)
(user.firstname || '') + ' ' + (user.lastname || '')
user = App.Collection.find( 'User', user_id )
user.realname
content: ->
user_id = $(@).data('id')
user = App.User.find(user_id)
user = App.Collection.find( 'User', user_id )
# get display data
data = []
@ -322,9 +350,8 @@ class App.Controller extends Spine.Controller
type = $(@).filter('[data-type]').data('type')
data = tickets[type] || []
# set human time
for ticket in data
# set human time
ticket.humanTime = controller.humanTime(ticket.created_at)
# insert data
@ -333,129 +360,6 @@ class App.Controller extends Spine.Controller
)
)
loadCollection: (params) ->
# remember in store if not already requested
if !params.localStorage
if params.type == 'User'
for user_id, user of params.data
data = {}
data[params.type] = {}
data[params.type][ user_id ] = user
App.Store.write( 'collection::' + params.type + '::' + user_id, { type: params.type, localStorage: true, collections: data } )
else
for object in params.data
data = {}
data[params.type] = [ object ]
App.Store.write( 'collection::' + params.type + '::' + object.id, { type: params.type, localStorage: true, collections: data } )
# users
if params.type == 'User'
for user_id, user of params.data
# set socal media links
if user['accounts']
for account of user['accounts']
if account == 'twitter'
user['accounts'][account]['link'] = 'http://twitter.com/' + user['accounts'][account]['username']
if account == 'facebook'
user['accounts'][account]['link'] = 'https://www.facebook.com/profile.php?id=' + user['accounts'][account]['uid']
# set image url
if user && !user['image']
user['image'] = 'http://placehold.it/48x48'
# set realname
user['realname'] = ''
if user['firstname']
user['realname'] = user['firstname']
if user['lastname']
if user['realname'] isnt ''
user['realname'] = user['realname'] + ' '
user['realname'] = user['realname'] + user['lastname']
# load in collection if needed
if !params.collection
App.User.refresh( user, options: { clear: true } )
# tickets
else if params.type == 'Ticket'
for ticket in params.data
# set human time
ticket.humanTime = @humanTime(ticket.created_at)
# priority
ticket.ticket_priority = App.TicketPriority.find(ticket.ticket_priority_id)
# state
ticket.ticket_state = App.TicketState.find(ticket.ticket_state_id)
# group
ticket.group = App.Group.find(ticket.group_id)
# customer
if ticket.customer_id and App.User.exists(ticket.customer_id)
user = App.User.find(ticket.customer_id)
ticket.customer = user
# owner
if ticket.owner_id and App.User.exists(ticket.owner_id)
user = App.User.find(ticket.owner_id)
ticket.owner = user
# load in collection if needed
if !params.collection
App.Ticket.refresh( ticket, options: { clear: true } )
# articles
else if params.type == 'TicketArticle'
for article in params.data
# add user
article.created_by = App.User.find(article.created_by_id)
# set human time
article.humanTime = @humanTime(article.created_at)
# add possible actions
article.article_type = App.TicketArticleType.find( article.ticket_article_type_id )
article.article_sender = App.TicketArticleSender.find( article.ticket_article_sender_id )
# load in collection if needed
if !params.collection
App.TicketArticle.refresh( article, options: { clear: true } )
# history
else if params.type == 'History'
for histroy in params.data
# add user
histroy.created_by = App.User.find(histroy.created_by_id)
# set human time
histroy.humanTime = @humanTime(histroy.created_at)
# add possible actions
if histroy.history_attribute_id
histroy.attribute = App.HistoryAttribute.find( histroy.history_attribute_id )
if histroy.history_type_id
histroy.type = App.HistoryType.find( histroy.history_type_id )
if histroy.history_object_id
histroy.object = App.HistoryObject.find( histroy.history_object_id )
# load in collection if needed
if !params.collection
App.History.refresh( histroy, options: { clear: true } )
# all the rest
else
for object in params.data
# load in collection if needed
if !params.collection
App[params.type].refresh( object, options: { clear: true } )
ws_send: (data) ->
Spine.trigger( 'ws:send', JSON.stringify(data) )

View file

@ -418,7 +418,8 @@ class App.ControllerForm extends App.Controller
# if record.name.toString() is attribute.value.toString()
# record.selected = 'selected'
# record.checked = 'checked'
if ( attribute.value && record.value && _.include(attribute.value, record.value) ) || ( attribute.value && record.name && _.include(attribute.value, record.name) )
else if ( attribute.value && record.value && _.include(attribute.value, record.value) ) || ( attribute.value && record.name && _.include(attribute.value, record.name) )
record.selected = 'selected'
record.checked = 'checked'

View file

@ -37,13 +37,13 @@ class App.DashboardActivityStream extends App.Controller
items = data.activity_stream
# load user collection
@loadCollection( type: 'User', data: data.users )
App.Collection.load( type: 'User', data: data.users )
# load ticket collection
@loadCollection( type: 'Ticket', data: data.tickets )
App.Collection.load( type: 'Ticket', data: data.tickets )
# load article collection
@loadCollection( type: 'TicketArticle', data: data.articles )
App.Collection.load( type: 'TicketArticle', data: data.articles )
@render(items)
@ -51,15 +51,15 @@ class App.DashboardActivityStream extends App.Controller
# load user data
for item in items
item.created_by = App.User.find( item.created_by_id )
item.created_by = App.Collection.find( 'User', item.created_by_id )
# load ticket data
for item in items
item.data = {}
if item.history_object is 'Ticket'
item.data.title = App.Ticket.find( item.o_id ).title
item.data.title = App.Collection.find( 'Ticket', item.o_id ).title
if item.history_object is 'Ticket::Article'
article = App.TicketArticle.find( item.o_id )
article = App.Collection.find( 'TicketArticle', item.o_id )
item.history_object = 'Article'
item.sub_o_id = article.id
item.o_id = article.ticket_id

View file

@ -24,10 +24,10 @@ class App.DashboardRecentViewed extends App.Controller
@items = data.recent_viewed
# load user collection
@loadCollection( type: 'User', data: data.users )
App.Collection.load( type: 'User', data: data.users )
# load ticket collection
@loadCollection( type: 'Ticket', data: data.tickets )
App.Collection.load( type: 'Ticket', data: data.tickets )
@render()
)
@ -36,13 +36,11 @@ class App.DashboardRecentViewed extends App.Controller
# load user data
for item in @items
# @log 'load', item.created_by_id
item.created_by = App.User.find(item.created_by_id)
item.created_by = App.Collection.find( 'User', item.created_by_id )
# load ticket data
for item in @items
# @log 'load', item.o_id
item.ticket = App.Ticket.find(item.o_id)
item.ticket = App.Collection.find( 'User', item.o_id )
html = App.view('dashboard/recent_viewed')(
head: 'Recent Viewed',

View file

@ -84,7 +84,7 @@ class App.DashboardTicket extends App.Controller
while i < end
i = i + 1
if @ticket_list[ i - 1 ]
@tickets_in_table.push App.Ticket.find( @ticket_list[ i - 1 ] )
@tickets_in_table.push App.Collection.find( 'Ticket', @ticket_list[ i - 1 ] )
shown_all_attributes = @ticketTableAttributes( App.Overview.find(@overview.id).view.d.overview )
table = @table(
@ -102,6 +102,9 @@ class App.DashboardTicket extends App.Controller
html.find('.table-overview').append(table)
@html html
# show frontend times
@frontendTimeUpdate()
# start user popups
@userPopups()

View file

@ -37,7 +37,7 @@ class Index extends App.Controller
@edit_form = cache.edit_form
# load user collection
@loadCollection( type: 'User', data: cache.users )
App.Collection.load( type: 'User', data: cache.users )
@render()
else
@ -59,18 +59,18 @@ class Index extends App.Controller
@edit_form = data.edit_form
# load user collection
@loadCollection( type: 'User', data: data.users )
App.Collection.load( type: 'User', data: data.users )
# load ticket collection
if data.ticket && data.articles
@loadCollection( type: 'Ticket', data: [data.ticket] )
App.Collection.load( type: 'Ticket', data: [data.ticket] )
# load article collections
@loadCollection( type: 'TicketArticle', data: data.articles || [] )
App.Collection.load( type: 'TicketArticle', data: data.articles || [] )
# render page
t = App.Ticket.find(params.ticket_id).attributes()
a = App.TicketArticle.find(params.article_id)
t = App.Collection.find( 'Ticket', params.ticket_id ).attributes()
a = App.Collection.find( 'TicketArticle', params.article_id )
# reset owner
t.owner_id = 0
@ -86,9 +86,9 @@ class Index extends App.Controller
# set defaults
defaults = template['options'] || {}
if !( 'ticket_state_id' of defaults )
defaults['ticket_state_id'] = App.TicketState.findByAttribute( 'name', 'new' ).id
defaults['ticket_state_id'] = App.Collection.findByAttribute( 'TicketState', 'name', 'new' ).id
if !( 'ticket_priority_id' of defaults )
defaults['ticket_priority_id'] = App.TicketPriority.findByAttribute( 'name', '2 normal' ).id
defaults['ticket_priority_id'] = App.Collection.findByAttribute( 'TicketPriority', 'name', '2 normal' ).id
# remember customers
if $('#create_customer_id').val()
@ -163,10 +163,10 @@ class Index extends App.Controller
@log 'updateAttributes', params
# find sender_id
sender = App.TicketArticleSender.findByAttribute( 'name', 'Customer' )
type = App.TicketArticleType.findByAttribute( 'name', 'phone' )
sender = App.Collection.findByAttribute( 'TicketArticleSender', 'name', 'Customer' )
type = App.Collection.findByAttribute( 'TicketArticleType', 'name', 'phone' )
if params.group_id
group = App.Group.find(params.group_id)
group = App.Collection.find( 'Group', params.group_id )
# create article
params['article'] = {
@ -248,7 +248,7 @@ class UserNew extends App.ControllerModal
user = new App.User
# find role_id
role = App.Role.findByAttribute( 'name', 'Customer' )
role = App.Collection.findByAttribute( 'Role', 'name', 'Customer' )
params.role_ids = role.id
@log 'updateAttributes', params
user.load(params)

View file

@ -14,23 +14,23 @@ class App.TicketHistory extends App.ControllerModal
@ticket = data.ticket
# load user collection
@loadCollection( type: 'User', data: data.users )
App.Collection.load( type: 'User', data: data.users )
# load ticket collection
@loadCollection( type: 'Ticket', data: [data.ticket] )
App.Collection.load( type: 'Ticket', data: [data.ticket] )
# load history_type collections
@loadCollection( type: 'HistoryType', data: data.history_types )
App.Collection.load( type: 'HistoryType', data: data.history_types )
# load history_object collections
@loadCollection( type: 'HistoryObject', data: data.history_objects )
App.Collection.load( type: 'HistoryObject', data: data.history_objects )
# load history_attributes collections
@loadCollection( type: 'HistoryAttribute', data: data.history_attributes )
App.Collection.load( type: 'HistoryAttribute', data: data.history_attributes )
# load history collections
App.History.deleteAll()
@loadCollection( type: 'History', data: data.history )
App.Collection.deleteAll( 'History' )
App.Collection.load( type: 'History', data: data.history )
# render page
@render()
@ -39,7 +39,7 @@ class App.TicketHistory extends App.ControllerModal
render: ->
@html App.view('agent_ticket_history')(
objects: App.History.all(),
objects: App.Collection.all( 'History' ),
)
@modalShow()

View file

@ -24,13 +24,16 @@ class App.TicketMerge extends App.ControllerModal
success: (data, status, xhr) =>
if data['result'] is 'success'
@loadCollection( type: 'Ticket', data: [data.master_ticket] )
@loadCollection( type: 'Ticket', data: [data.slave_ticket] )
# update collection
App.Collection.load( type: 'Ticket', data: [data.master_ticket] )
App.Collection.load( type: 'Ticket', data: [data.slave_ticket] )
# hide dialog
@modalHide()
# view ticket
@log 'nav...', App.Ticket.find( data.master_ticket['id'] )
@log 'nav...', App.Collection.find( 'Ticket', data.master_ticket['id'] )
@navigate '#ticket/zoom/' + data.master_ticket['id']
# notify UI

View file

@ -77,7 +77,7 @@ class Index extends App.Controller
@ticket_list_show = []
for ticket_id in @ticket_list
@ticket_list_show.push App.Ticket.find(ticket_id)
@ticket_list_show.push App.Collection.find( 'Ticket', ticket_id )
# remeber bulk attributes
@bulk = data.bulk
@ -159,6 +159,9 @@ class Index extends App.Controller
# start user popups
@userPopups()
# show frontend times
@frontendTimeUpdate()
# start bulk action observ
@el.find('.bulk-action').append( @bulk_form() )

View file

@ -54,28 +54,26 @@ class Index extends App.Controller
@edit_form = data.edit_form
# load user collection
@loadCollection( type: 'User', data: data.users )
App.Collection.load( type: 'User', data: data.users )
# load ticket collection
@loadCollection( type: 'Ticket', data: [data.ticket] )
App.Collection.load( type: 'Ticket', data: [data.ticket] )
# load article collections
@loadCollection( type: 'TicketArticle', data: data.articles || [] )
App.Collection.load( type: 'TicketArticle', data: data.articles )
# render page
@render()
render: =>
return if !App.Ticket.exists(@ticket_id)
# get data
if !@ticket
@ticket = App.Ticket.find(@ticket_id)
@ticket = App.Collection.find( 'Ticket', @ticket_id )
if !@articles
@articles = []
for article_id in @ticket.article_ids
article = App.TicketArticle.find(article_id)
article = App.Collection.find( 'TicketArticle', article_id )
@articles.push article
# rework articles
@ -146,6 +144,9 @@ class Index extends App.Controller
# show ticket action row
@ticket_action_row()
# show frontend times
@frontendTimeUpdate()
# scrall to article if given
if @article_id
offset = document.getElementById( 'article-' + @article_id ).offsetTop
@ -345,11 +346,11 @@ class Index extends App.Controller
# find sender_id
if @isRole('Customer')
sender = App.TicketArticleSender.findByAttribute( 'name', 'Customer' )
type = App.TicketArticleType.findByAttribute( 'name', 'web' )
sender = App.Collection.findByAttribute( 'TicketArticleSender', 'name', 'Customer' )
type = App.Collection.findByAttribute( 'TicketArticleType', 'name', 'web' )
params['ticket_article_type_id'] = type.id
else
sender = App.TicketArticleSender.findByAttribute( 'name', 'Agent' )
sender = App.Collection.findByAttribute( 'TicketArticleSender', 'name', 'Agent' )
params.ticket_article_sender_id = sender.id
@log 'updateAttributes', params, sender, sender.id
article.load(params)

View file

@ -31,7 +31,7 @@ class Index extends App.Controller
@edit_form = cache.edit_form
# load user collection
@loadCollection( type: 'User', data: cache.users )
App.Collection.load( type: 'User', data: cache.users )
@render()
else
@ -53,18 +53,18 @@ class Index extends App.Controller
@edit_form = data.edit_form
# load user collection
@loadCollection( type: 'User', data: data.users )
App.Collection.load( type: 'User', data: data.users )
# load ticket collection
if data.ticket && data.articles
@loadCollection( type: 'Ticket', data: [data.ticket] )
App.Collection.load( type: 'Ticket', data: [data.ticket] )
# load article collections
@loadCollection( type: 'TicketArticle', data: data.articles || [] )
App.Collection.load( type: 'TicketArticle', data: data.articles || [] )
# render page
t = App.Ticket.find(params.ticket_id).attributes()
a = App.TicketArticle.find(params.article_id)
t = App.Collection.find( 'Ticket', params.ticket_id ).attributes()
a = App.Collection.find( 'TicketArticle', params.article_id )
# reset owner
t.owner_id = 0
@ -80,9 +80,9 @@ class Index extends App.Controller
# set defaults
defaults = template['options'] || {}
if !( 'ticket_state_id' of defaults )
defaults['ticket_state_id'] = App.TicketState.findByAttribute( 'name', 'new' )
defaults['ticket_state_id'] = App.Collection.findByAttribute( 'TicketState', 'name', 'new' )
if !( 'ticket_priority_id' of defaults )
defaults['ticket_priority_id'] = App.TicketPriority.findByAttribute( 'name', '2 normal' )
defaults['ticket_priority_id'] = App.Collection.findByAttribute( 'TicketPriority', 'name', '2 normal' )
# generate form
configure_attributes = [
@ -126,11 +126,11 @@ class Index extends App.Controller
params.customer_id = Session['id']
# set prio
priority = App.TicketPriority.findByAttribute( 'name', '2 normal' )
priority = App.Collection.findByAttribute( 'TicketPriority', 'name', '2 normal' )
params.ticket_state_id = priority.id
# set state
state = App.TicketState.findByAttribute( 'name', 'new' )
state = App.Collection.findByAttribute( 'TicketState', 'name', 'new' )
params.ticket_priority_id = state.id
# fillup params
@ -142,10 +142,10 @@ class Index extends App.Controller
@log 'updateAttributes', params
# find sender_id
sender = App.TicketArticleSender.findByAttribute( 'name', 'Customer' )
type = App.TicketArticleType.findByAttribute( 'name', 'web' )
sender = App.Collection.findByAttribute( 'TicketArticleSender', 'name', 'Customer' )
type = App.Collection.findByAttribute( 'TicketArticleType', 'name', 'web' )
if params.group_id
group = App.Group.find(params.group_id)
group = App.Collection.find( 'Group', params.group_id )
# create article
params['article'] = {

View file

@ -35,7 +35,7 @@ class Index extends App.Controller
@master_user = data.master_user
# load group collection
@loadCollection( type: 'Group', data: data.groups )
App.Collection.load( type: 'Group', data: data.groups )
# render page
@render()
@ -80,7 +80,7 @@ class Index extends App.Controller
@params.invite = true
# find agent role
role = App.Role.findByAttribute('name', 'Agent')
role = App.Collection.findByAttribute( 'Role', 'name', 'Agent' )
if role
@params.role_ids = role.id
else

View file

@ -26,10 +26,10 @@ class App.LinkInfo extends App.Controller
@links = data.links
# load user collection
@loadCollection( type: 'User', data: data.users )
App.Collection.load( type: 'User', data: data.users )
# load ticket collection
@loadCollection( type: 'Ticket', data: data.tickets )
App.Collection.load( type: 'Ticket', data: data.tickets )
@render()
)
@ -42,7 +42,7 @@ class App.LinkInfo extends App.Controller
list[ item['link_type'] ] = []
if item['link_object'] is 'Ticket'
ticket = App.Ticket.find( item['link_object_value'] )
ticket = App.Collection.find( 'Ticket', item['link_object_value'] )
if ticket.ticket_state.name is 'merged'
ticket.css = 'merged'
list[ item['link_type'] ].push ticket

View file

@ -170,10 +170,10 @@ class App.Navigation extends App.Controller
items = data.recent_viewed
# load user collection
@loadCollection( type: 'User', data: data.users )
App.Collection.load( type: 'User', data: data.users )
# load ticket collection
@loadCollection( type: 'Ticket', data: data.tickets )
App.Collection.load( type: 'Ticket', data: data.tickets )
# remove old views
for key of Config.NavBarRight
@ -190,7 +190,7 @@ class App.Navigation extends App.Controller
if prio is 8000
divider = true
navheader = 'Recent Viewed'
ticket = App.Ticket.find(item.o_id)
ticket = App.Collection.find( 'Ticket', item.o_id )
prio++
Config.NavBarRight['RecendViewed::' + ticket.id + '-' + prio ] = {
prio: prio,

View file

@ -11,7 +11,7 @@ class App.TemplateUI extends App.Controller
# fetch item on demand
fetch_needed = 1
if App.Template.count() > 0
if App.Collection.count( 'Template' ) > 0
fetch_needed = 0
@render()
@ -23,7 +23,7 @@ class App.TemplateUI extends App.Controller
@log 'loading....'
@render()
App.Template.unbind 'refresh'
App.Template.fetch()
App.Collection.fetch( 'Template' )
render: =>
@configure_attributes = [
@ -32,7 +32,7 @@ class App.TemplateUI extends App.Controller
template = {}
if @template_id
template = App.Template.find(@template_id)
template = App.Collection.find( 'Template', @template_id )
# insert data
@html App.view('template')(
@ -49,7 +49,7 @@ class App.TemplateUI extends App.Controller
# get params
params = @formParam(e.target)
template = App.Template.find( params['template_id'] )
template = App.Collection.find( 'Template', params['template_id'] )
if confirm('Sure?')
template.destroy()
@template_id = undefined
@ -61,7 +61,7 @@ class App.TemplateUI extends App.Controller
# get params
params = @formParam(e.target)
template = App.Template.find( params['template_id'] )
template = App.Collection.find( 'Template', params['template_id'] )
Spine.trigger 'ticket_create_rerender', template.attributes()
create: (e) =>
@ -72,7 +72,7 @@ class App.TemplateUI extends App.Controller
name = params['template_name']
# delete params['template_name']
template = App.Template.findByAttribute( 'name', name )
template = App.Collection.findByAttribute( 'Template', 'name', name )
if !template
template = new App.Template

View file

@ -6,29 +6,9 @@ class App.UserInfo extends App.Controller
constructor: ->
super
App.Collection.find( 'User', @user_id, @render )
# fetch item on demand
fetch_needed = 1
if App.User.exists(@user_id)
@log 'exists.user...', @user_id
fetch_needed = 0
@render(@user_id)
if fetch_needed
@reload(@user_id)
reload: (user_id) =>
App.User.bind 'refresh', =>
@log 'loading.user...', user_id
App.User.unbind 'refresh'
@render(user_id)
App.User.fetch( id: user_id )
render: (user_id) ->
# load user collection
user = App.User.find(user_id)
@loadCollection( type: 'User', data: { new: user }, collection: true )
render: (user) =>
# get display data
data = []
@ -39,20 +19,20 @@ class App.UserInfo extends App.Controller
# insert data
@html App.view('user_info')(
user: App.User.find(user_id),
user: user,
data: data,
)
@userTicketPopups(
selector: '.user-tickets',
user_id: user_id,
user_id: user.id,
)
update: (e) =>
# update changes
note = $(e.target).parent().find('[data-type=edit]').val()
user = App.User.find(@user_id)
user = App.Collection.find( 'User', @user_id )
if user.note isnt note
user.updateAttributes( note: note )
@log 'update', e, note, user

View file

@ -13,7 +13,7 @@
#= require ./lib/bootstrap-tab.js
#= require ./lib/bootstrap-transition.js
#= require ./lib/underscore.coffee
#= require ./lib/underscore-1.3.3.js
#= require ./lib/ba-linkify.js
#= require ./lib/jquery.tagsinput.js
#= require ./lib/jquery.noty.js
@ -28,6 +28,7 @@
#= require ./lib/auth.js.coffee
#= require ./lib/i18n.js.coffee
#= require ./lib/store.js.coffee
#= require ./lib/collection.js.coffee
#= require_tree ./models
#= require_tree ./controllers
#= require_tree ./views

View file

@ -65,9 +65,8 @@ class App.Auth
App.WebSocket.auth()
# refresh/load default collections
controller = new App.Controller
for key, value of data.default_collections
controller.loadCollection( type: key, data: value )
App.Collection.load( type: key, data: value )
# rebuild navbar with new navbar items
Spine.trigger 'navrebuild', data.session

View file

@ -0,0 +1,222 @@
class App.Collection
_instance = undefined
@init: ->
_instance = new _Singleton
@load: ( args ) ->
if _instance == undefined
_instance ?= new _Singleton
_instance.load( args )
@find: ( type, id, callback ) ->
if _instance == undefined
_instance ?= new _Singleton
_instance.find( type, id, callback )
@get: ( args ) ->
if _instance == undefined
_instance ?= new _Singleton
_instance.get( args )
@all: ( type ) ->
if _instance == undefined
_instance ?= new _Singleton
_instance.all( type )
@deleteAll: ( type ) ->
if _instance == undefined
_instance ?= new _Singleton
_instance.deleteAll( type )
@findByAttribute: ( type, key, value ) ->
if _instance == undefined
_instance ?= new _Singleton
_instance.findByAttribute( type, key, value )
@count: ( type ) ->
if _instance == undefined
_instance ?= new _Singleton
_instance.count( type )
@fetch: ( type ) ->
if _instance == undefined
_instance ?= new _Singleton
_instance.fetch( type )
class _Singleton
constructor: (@args) ->
# add trigger - bind new events
Spine.bind 'loadCollection', (data) =>
# load collections
if data.collections
for type of data.collections
console.log 'loadCollection:trigger', type, data.collections[type]
@load( localStorage: data.localStorage, type: type, data: data.collections[type] )
# find collections to load
@_loadCollectionAll()
_loadCollectionAll: ->
list = App.Store.list()
for key in list
parts = key.split('::')
if parts[0] is 'collection'
data = App.Store.get( key )
if data && data.localStorage
console.log('load INIT', data)
@load( data )
load: (params) ->
console.log( 'load', params )
return if _.isEmpty( params.data )
if _.isArray( params.data )
for object in params.data
console.log( 'load ARRAY', object)
App[params.type].refresh( object, options: { clear: true } )
# remember in store if not already requested from local storage
if !params.localStorage
App.Store.write( 'collection::' + params.type + '::' + object.id, { type: params.type, localStorage: true, data: [ object ] } )
return
# if _.isObject( params.data )
for key, object of params.data
console.log( 'load OB', object)
App[params.type].refresh( object, options: { clear: true } )
# remember in store if not already requested from local storage
if !params.localStorage
App.Store.write( 'collection::' + params.type + '::' + object.id, { type: params.type, localStorage: true, data: [ object ] } )
find: ( type, id, callback ) ->
console.log( 'find', type, id )
# if App[type].exists( id ) && !callback
if App[type].exists( id )
console.log( 'find exists', type, id )
data = App[type].find( id )
if callback
callback( data )
else
console.log( 'find not loaded!', type, id )
if callback
App[type].bind 'refresh', ->
console.log 'loaded..' + type + '..', id
App[type].unbind 'refresh'
data = App.Collection.find( type, id )
callback( data )
console.log 'loading..' + type + '..', id
App[type].fetch( id: id )
return true
return false
# users
if type == 'User'
# set socal media links
if data['accounts']
for account of data['accounts']
if account == 'twitter'
data['accounts'][account]['link'] = 'http://twitter.com/' + data['accounts'][account]['username']
if account == 'facebook'
data['accounts'][account]['link'] = 'https://www.facebook.com/profile.php?id=' + data['accounts'][account]['uid']
# set image url
if data && !data['image']
data['image'] = 'http://placehold.it/48x48'
# set realname
data['realname'] = ''
if data['firstname']
data['realname'] = data['firstname']
if data['lastname']
if data['realname'] isnt ''
data['realname'] = data['realname'] + ' '
data['realname'] = data['realname'] + data['lastname']
return data
# tickets
else if type == 'Ticket'
# priority
data.ticket_priority = @find( 'TicketPriority', data.ticket_priority_id )
# state
data.ticket_state = @find( 'TicketState', data.ticket_state_id )
# group
data.group = @find( 'Group', data.group_id )
# customer
if data.customer_id
data.customer = @find( 'User', data.customer_id )
# owner
if data.owner_id
data.owner = @find( 'User', data.owner_id )
# add created & updated
if data.created_by_id
data.created_by = @find( 'User', data.created_by_id )
if data.updated_by_id
data.updated_by = @find( 'User', data.updated_by_id )
return data
# articles
else if type == 'TicketArticle'
# add created & updated
data.created_by = @find( 'User', data.created_by_id )
# add possible actions
data.article_type = @find( 'TicketArticleType', data.ticket_article_type_id )
data.article_sender = @find( 'TicketArticleSender', data.ticket_article_sender_id )
return data
# history
else if type == 'History'
# add user
data.created_by = @find( 'User', data.created_by_id )
# add possible actions
if data.history_attribute_id
data.attribute = @find( 'HistoryAttribute', data.history_attribute_id )
if data.history_type_id
data.type = @find( 'HistoryType', data.history_type_id )
if data.history_object_id
data.object = @find( 'HistoryObject', data.history_object_id )
return data
else
return data
get: (params) ->
console.log('get')
App[params.type].refresh( object, options: { clear: true } )
all: (type) ->
App[type].all()
deleteAll: (type) ->
App[type].deleteAll()
findByAttribute: ( type, key, value ) ->
App[type].findByAttribute( key, value )
count: ( type ) ->
App[type].count()
fetch: ( type ) ->
App[type].fetch()

View file

@ -5,36 +5,26 @@ class App.Run extends App.Controller
@log 'RUN app'
@el = $('#app')
# init collections
App.Collection.init()
# create web socket connection
App.WebSocket.connect()
# init of i18n
new App.i18n
# bind new events
Spine.bind 'loadCollection', (data) =>
# load collections
if data.collections
for key of data.collections
@log 'loadCollection', key, data.collections[key]
@loadCollection( localStorage: data.localStorage, type: key, data: data.collections[key] )
# load collections
App.Store.load()
# start navigation controller
new App.Navigation( el: @el.find('#navigation') );
new App.Navigation( el: @el.find('#navigation') )
# check if session already exists/try to get session data from server
App.Auth.loginCheck()
# start notify controller
new App.Notify( el: @el.find('#notify') );
new App.Notify( el: @el.find('#notify') )
# start content
new App.Content( el: @el.find('#content') );
new App.Content( el: @el.find('#content') )
# bind to fill selected text into
$(@el).bind('mouseup', =>

View file

@ -3,10 +3,6 @@ class App.Store
@renew: ->
_instance = new _Singleton
@load: ->
if _instance == undefined
_instance ?= new _Singleton
@write: (key, value) ->
if _instance == undefined
_instance ?= new _Singleton
@ -22,10 +18,10 @@ class App.Store
_instance ?= new _Singleton
_instance.delete(args)
@clear: (args) ->
@clear: ->
if _instance == undefined
_instance ?= new _Singleton
_instance.clear(args)
_instance.clear()
@list: () ->
if _instance == undefined
@ -34,94 +30,32 @@ class App.Store
# The actual Singleton class
class _Singleton
store: {}
constructor: (@args) ->
# write to local storage
write: (key, value) ->
localStorage.setItem( key, JSON.stringify( value ) )
# find collections to load
@_loadCollectionAll()
@_loadCollectionType('TicketPriority')
@_loadCollectionType('TicketStateType')
@_loadCollectionType('TicketState')
@_loadCollectionType('TicketArticleSender')
@_loadCollectionType('TicketArticleType')
@_loadCollectionType('Group')
@_loadCollectionType('Role')
@_loadCollectionType('Organization')
@_loadCollectionType('User')
@_loadCollectionType()
# get item
get: (key) ->
value = localStorage.getItem( key )
return if !value
object = JSON.parse( value )
return object
_loadCollectionAll: ->
@all = {}
@rest = {}
# delete item
delete: (key) ->
localStorage.removeItem( key )
# clear local storage
clear: ->
localStorage.clear()
# return list of all keys
list: ->
list = []
logLength = localStorage.length-1;
for count in [0..logLength]
key = localStorage.key( count )
if key
value = localStorage.getItem( key )
data = JSON.parse( value )
@all[key] = data
_loadCollectionType: (type) ->
# console.log('STORE NEW' + logLength)
toGo = @all
if !_.isEmpty( @rest )
toGo = _.clone( @rest )
@rest = {}
for key, data of toGo
# console.log('STORE NEW' + count + '-' + key, data)
if data['collections']
data['localStorage'] = true
if type
if data['type'] is type
@_loadCollection(data)
else
@rest[key] = data
else
@_loadCollection(data)
_loadCollection: (data) ->
console.log('fire', 'loadCollection', data )
Spine.trigger( 'loadCollection', data )
write: (key, value) ->
# write to instance
@store[ key ] = value
# write to local storage
localStorage.setItem( key, JSON.stringify( value ) )
get: (key) ->
# return from instance
return @store[ key ] if @store[ key ]
# if not, return from local storage
value = localStorage.getItem( key )
object = JSON.parse( value )
return object if object
# return undefined if not in storage
return undefined
delete: (key) ->
delete @store[ key ]
clear: (action) ->
console.log 'Store:clear', action
# clear instance data
@store = {}
# clear local storage
if action is 'all'
localStorage.clear()
list: () ->
list = []
for key of @store
list.push key
list.push key
list

File diff suppressed because it is too large Load diff

View file

@ -1,682 +0,0 @@
# **Underscore.coffee
# (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.**
# Underscore is freely distributable under the terms of the
# [MIT license](http://en.wikipedia.org/wiki/MIT_License).
# Portions of Underscore are inspired by or borrowed from
# [Prototype.js](http://prototypejs.org/api), Oliver Steele's
# [Functional](http://osteele.com), and John Resig's
# [Micro-Templating](http://ejohn.org).
# For all details and documentation:
# http://documentcloud.github.com/underscore/
# Baseline setup
# --------------
# Establish the root object, `window` in the browser, or `global` on the server.
root = this
# Save the previous value of the `_` variable.
previousUnderscore = root._
# Establish the object that gets thrown to break out of a loop iteration.
# `StopIteration` is SOP on Mozilla.
breaker = if typeof(StopIteration) is 'undefined' then '__break__' else StopIteration
# Helper function to escape **RegExp** contents, because JS doesn't have one.
escapeRegExp = (string) -> string.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1')
# Save bytes in the minified (but not gzipped) version:
ArrayProto = Array.prototype
ObjProto = Object.prototype
# Create quick reference variables for speed access to core prototypes.
slice = ArrayProto.slice
unshift = ArrayProto.unshift
toString = ObjProto.toString
hasOwnProperty = ObjProto.hasOwnProperty
propertyIsEnumerable = ObjProto.propertyIsEnumerable
# All **ECMA5** native implementations we hope to use are declared here.
nativeForEach = ArrayProto.forEach
nativeMap = ArrayProto.map
nativeReduce = ArrayProto.reduce
nativeReduceRight = ArrayProto.reduceRight
nativeFilter = ArrayProto.filter
nativeEvery = ArrayProto.every
nativeSome = ArrayProto.some
nativeIndexOf = ArrayProto.indexOf
nativeLastIndexOf = ArrayProto.lastIndexOf
nativeIsArray = Array.isArray
nativeKeys = Object.keys
# Create a safe reference to the Underscore object for use below.
_ = (obj) -> new wrapper(obj)
# Export the Underscore object for **CommonJS**.
if typeof(exports) != 'undefined' then exports._ = _
# Export Underscore to global scope.
root._ = _
# Current version.
_.VERSION = '1.1.0'
# Collection Functions
# --------------------
# The cornerstone, an **each** implementation.
# Handles objects implementing **forEach**, arrays, and raw objects.
_.each = (obj, iterator, context) ->
try
if nativeForEach and obj.forEach is nativeForEach
obj.forEach iterator, context
else if _.isNumber obj.length
iterator.call context, obj[i], i, obj for i in [0...obj.length]
else
iterator.call context, val, key, obj for own key, val of obj
catch e
throw e if e isnt breaker
obj
# Return the results of applying the iterator to each element. Use JavaScript
# 1.6's version of **map**, if possible.
_.map = (obj, iterator, context) ->
return obj.map(iterator, context) if nativeMap and obj.map is nativeMap
results = []
_.each obj, (value, index, list) ->
results.push iterator.call context, value, index, list
results
# **Reduce** builds up a single result from a list of values. Also known as
# **inject**, or **foldl**. Uses JavaScript 1.8's version of **reduce**, if possible.
_.reduce = (obj, iterator, memo, context) ->
if nativeReduce and obj.reduce is nativeReduce
iterator = _.bind iterator, context if context
return obj.reduce iterator, memo
_.each obj, (value, index, list) ->
memo = iterator.call context, memo, value, index, list
memo
# The right-associative version of **reduce**, also known as **foldr**. Uses
# JavaScript 1.8's version of **reduceRight**, if available.
_.reduceRight = (obj, iterator, memo, context) ->
if nativeReduceRight and obj.reduceRight is nativeReduceRight
iterator = _.bind iterator, context if context
return obj.reduceRight iterator, memo
reversed = _.clone(_.toArray(obj)).reverse()
_.reduce reversed, iterator, memo, context
# Return the first value which passes a truth test.
_.detect = (obj, iterator, context) ->
result = null
_.each obj, (value, index, list) ->
if iterator.call context, value, index, list
result = value
_.breakLoop()
result
# Return all the elements that pass a truth test. Use JavaScript 1.6's
# **filter**, if it exists.
_.filter = (obj, iterator, context) ->
return obj.filter iterator, context if nativeFilter and obj.filter is nativeFilter
results = []
_.each obj, (value, index, list) ->
results.push value if iterator.call context, value, index, list
results
# Return all the elements for which a truth test fails.
_.reject = (obj, iterator, context) ->
results = []
_.each obj, (value, index, list) ->
results.push value if not iterator.call context, value, index, list
results
# Determine whether all of the elements match a truth test. Delegate to
# JavaScript 1.6's **every**, if it is present.
_.every = (obj, iterator, context) ->
iterator ||= _.identity
return obj.every iterator, context if nativeEvery and obj.every is nativeEvery
result = true
_.each obj, (value, index, list) ->
_.breakLoop() unless (result = result and iterator.call(context, value, index, list))
result
# Determine if at least one element in the object matches a truth test. Use
# JavaScript 1.6's **some**, if it exists.
_.some = (obj, iterator, context) ->
iterator ||= _.identity
return obj.some iterator, context if nativeSome and obj.some is nativeSome
result = false
_.each obj, (value, index, list) ->
_.breakLoop() if (result = iterator.call(context, value, index, list))
result
# Determine if a given value is included in the array or object,
# based on `===`.
_.include = (obj, target) ->
return _.indexOf(obj, target) isnt -1 if nativeIndexOf and obj.indexOf is nativeIndexOf
return true for own key, val of obj when val is target
false
# Invoke a method with arguments on every item in a collection.
_.invoke = (obj, method) ->
args = _.rest arguments, 2
(if method then val[method] else val).apply(val, args) for val in obj
# Convenience version of a common use case of **map**: fetching a property.
_.pluck = (obj, key) ->
_.map(obj, (val) -> val[key])
# Return the maximum item or (item-based computation).
_.max = (obj, iterator, context) ->
return Math.max.apply(Math, obj) if not iterator and _.isArray(obj)
result = computed: -Infinity
_.each obj, (value, index, list) ->
computed = if iterator then iterator.call(context, value, index, list) else value
computed >= result.computed and (result = {value: value, computed: computed})
result.value
# Return the minimum element (or element-based computation).
_.min = (obj, iterator, context) ->
return Math.min.apply(Math, obj) if not iterator and _.isArray(obj)
result = computed: Infinity
_.each obj, (value, index, list) ->
computed = if iterator then iterator.call(context, value, index, list) else value
computed < result.computed and (result = {value: value, computed: computed})
result.value
# Sort the object's values by a criterion produced by an iterator.
_.sortBy = (obj, iterator, context) ->
_.pluck(((_.map obj, (value, index, list) ->
{value: value, criteria: iterator.call(context, value, index, list)}
).sort((left, right) ->
a = left.criteria; b = right.criteria
if a < b then -1 else if a > b then 1 else 0
)), 'value')
# Use a comparator function to figure out at what index an object should
# be inserted so as to maintain order. Uses binary search.
_.sortedIndex = (array, obj, iterator) ->
iterator ||= _.identity
low = 0
high = array.length
while low < high
mid = (low + high) >> 1
if iterator(array[mid]) < iterator(obj) then low = mid + 1 else high = mid
low
# Convert anything iterable into a real, live array.
_.toArray = (iterable) ->
return [] if (!iterable)
return iterable.toArray() if (iterable.toArray)
return iterable if (_.isArray(iterable))
return slice.call(iterable) if (_.isArguments(iterable))
_.values(iterable)
# Return the number of elements in an object.
_.size = (obj) -> _.toArray(obj).length
# Array Functions
# ---------------
# Get the first element of an array. Passing `n` will return the first N
# values in the array. Aliased as **head**. The `guard` check allows it to work
# with **map**.
_.first = (array, n, guard) ->
if n and not guard then slice.call(array, 0, n) else array[0]
# Returns everything but the first entry of the array. Aliased as **tail**.
# Especially useful on the arguments object. Passing an `index` will return
# the rest of the values in the array from that index onward. The `guard`
# check allows it to work with **map**.
_.rest = (array, index, guard) ->
slice.call(array, if _.isUndefined(index) or guard then 1 else index)
# Get the last element of an array.
_.last = (array) -> array[array.length - 1]
# Trim out all falsy values from an array.
_.compact = (array) -> item for item in array when item
# Return a completely flattened version of an array.
_.flatten = (array) ->
_.reduce array, (memo, value) ->
return memo.concat(_.flatten(value)) if _.isArray value
memo.push value
memo
, []
# Return a version of the array that does not contain the specified value(s).
_.without = (array) ->
values = _.rest arguments
val for val in _.toArray(array) when not _.include values, val
# Produce a duplicate-free version of the array. If the array has already
# been sorted, you have the option of using a faster algorithm.
_.uniq = (array, isSorted) ->
memo = []
for el, i in _.toArray array
memo.push el if i is 0 || (if isSorted is true then _.last(memo) isnt el else not _.include(memo, el))
memo
# Produce an array that contains every item shared between all the
# passed-in arrays.
_.intersect = (array) ->
rest = _.rest arguments
_.select _.uniq(array), (item) ->
_.all rest, (other) ->
_.indexOf(other, item) >= 0
# Zip together multiple lists into a single array -- elements that share
# an index go together.
_.zip = ->
length = _.max _.pluck arguments, 'length'
results = new Array length
for i in [0...length]
results[i] = _.pluck arguments, String i
results
# If the browser doesn't supply us with **indexOf** (I'm looking at you, MSIE),
# we need this function. Return the position of the first occurrence of an
# item in an array, or -1 if the item is not included in the array.
_.indexOf = (array, item) ->
return array.indexOf item if nativeIndexOf and array.indexOf is nativeIndexOf
i = 0; l = array.length
while l - i
if array[i] is item then return i else i++
-1
# Provide JavaScript 1.6's **lastIndexOf**, delegating to the native function,
# if possible.
_.lastIndexOf = (array, item) ->
return array.lastIndexOf(item) if nativeLastIndexOf and array.lastIndexOf is nativeLastIndexOf
i = array.length
while i
if array[i] is item then return i else i--
-1
# Generate an integer Array containing an arithmetic progression. A port of
# [the native Python **range** function](http://docs.python.org/library/functions.html#range).
_.range = (start, stop, step) ->
a = arguments
solo = a.length <= 1
i = start = if solo then 0 else a[0]
stop = if solo then a[0] else a[1]
step = a[2] or 1
len = Math.ceil((stop - start) / step)
return [] if len <= 0
range = new Array len
idx = 0
loop
return range if (if step > 0 then i - stop else stop - i) >= 0
range[idx] = i
idx++
i+= step
# Function Functions
# ------------------
# Create a function bound to a given object (assigning `this`, and arguments,
# optionally). Binding with arguments is also known as **curry**.
_.bind = (func, obj) ->
args = _.rest arguments, 2
-> func.apply obj or root, args.concat arguments
# Bind all of an object's methods to that object. Useful for ensuring that
# all callbacks defined on an object belong to it.
_.bindAll = (obj) ->
funcs = if arguments.length > 1 then _.rest(arguments) else _.functions(obj)
_.each funcs, (f) -> obj[f] = _.bind obj[f], obj
obj
# Delays a function for the given number of milliseconds, and then calls
# it with the arguments supplied.
_.delay = (func, wait) ->
args = _.rest arguments, 2
setTimeout((-> func.apply(func, args)), wait)
# Memoize an expensive function by storing its results.
_.memoize = (func, hasher) ->
memo = {}
hasher or= _.identity
->
key = hasher.apply this, arguments
return memo[key] if key of memo
memo[key] = func.apply this, arguments
# Defers a function, scheduling it to run after the current call stack has
# cleared.
_.defer = (func) ->
_.delay.apply _, [func, 1].concat _.rest arguments
# Returns the first function passed as an argument to the second,
# allowing you to adjust arguments, run code before and after, and
# conditionally execute the original function.
_.wrap = (func, wrapper) ->
-> wrapper.apply wrapper, [func].concat arguments
# Returns a function that is the composition of a list of functions, each
# consuming the return value of the function that follows.
_.compose = ->
funcs = arguments
->
args = arguments
for i in [funcs.length - 1..0] by -1
args = [funcs[i].apply(this, args)]
args[0]
# Object Functions
# ----------------
# Retrieve the names of an object's properties.
_.keys = nativeKeys or (obj) ->
return _.range 0, obj.length if _.isArray(obj)
key for key, val of obj
# Retrieve the values of an object's properties.
_.values = (obj) ->
_.map obj, _.identity
# Return a sorted list of the function names available in Underscore.
_.functions = (obj) ->
_.filter(_.keys(obj), (key) -> _.isFunction(obj[key])).sort()
# Extend a given object with all of the properties in a source object.
_.extend = (obj) ->
for source in _.rest(arguments)
obj[key] = val for key, val of source
obj
# Create a (shallow-cloned) duplicate of an object.
_.clone = (obj) ->
return obj.slice 0 if _.isArray obj
_.extend {}, obj
# Invokes interceptor with the obj, and then returns obj.
# The primary purpose of this method is to "tap into" a method chain, in order to perform operations on intermediate results within the chain.
_.tap = (obj, interceptor) ->
interceptor obj
obj
# Perform a deep comparison to check if two objects are equal.
_.isEqual = (a, b) ->
# Check object identity.
return true if a is b
# Different types?
atype = typeof(a); btype = typeof(b)
return false if atype isnt btype
# Basic equality test (watch out for coercions).
return true if `a == b`
# One is falsy and the other truthy.
return false if (!a and b) or (a and !b)
# One of them implements an `isEqual()`?
return a.isEqual(b) if a.isEqual
# Check dates' integer values.
return a.getTime() is b.getTime() if _.isDate(a) and _.isDate(b)
# Both are NaN?
return false if _.isNaN(a) and _.isNaN(b)
# Compare regular expressions.
if _.isRegExp(a) and _.isRegExp(b)
return a.source is b.source and
a.global is b.global and
a.ignoreCase is b.ignoreCase and
a.multiline is b.multiline
# If a is not an object by this point, we can't handle it.
return false if atype isnt 'object'
# Check for different array lengths before comparing contents.
return false if a.length and (a.length isnt b.length)
# Nothing else worked, deep compare the contents.
aKeys = _.keys(a); bKeys = _.keys(b)
# Different object sizes?
return false if aKeys.length isnt bKeys.length
# Recursive comparison of contents.
return false for key, val of a when !(key of b) or !_.isEqual(val, b[key])
true
# Is a given array or object empty?
_.isEmpty = (obj) ->
return obj.length is 0 if _.isArray(obj) or _.isString(obj)
return false for own key of obj
true
# Is a given value a DOM element?
_.isElement = (obj) -> obj and obj.nodeType is 1
# Is a given value an array?
_.isArray = nativeIsArray or (obj) -> !!(obj and obj.concat and obj.unshift and not obj.callee)
# Is a given variable an arguments object?
_.isArguments = (obj) -> obj and obj.callee
# Is the given value a function?
_.isFunction = (obj) -> !!(obj and obj.constructor and obj.call and obj.apply)
# Is the given value a string?
_.isString = (obj) -> !!(obj is '' or (obj and obj.charCodeAt and obj.substr))
# Is a given value a number?
_.isNumber = (obj) -> (obj is +obj) or toString.call(obj) is '[object Number]'
# Is a given value a boolean?
_.isBoolean = (obj) -> obj is true or obj is false
# Is a given value a Date?
_.isDate = (obj) -> !!(obj and obj.getTimezoneOffset and obj.setUTCFullYear)
# Is the given value a regular expression?
_.isRegExp = (obj) -> !!(obj and obj.exec and (obj.ignoreCase or obj.ignoreCase is false))
# Is the given value NaN -- this one is interesting. `NaN != NaN`, and
# `isNaN(undefined) == true`, so we make sure it's a number first.
_.isNaN = (obj) -> _.isNumber(obj) and window.isNaN(obj)
# Is a given value equal to null?
_.isNull = (obj) -> obj is null
# Is a given variable undefined?
_.isUndefined = (obj) -> typeof obj is 'undefined'
# Utility Functions
# -----------------
# Run Underscore.js in noConflict mode, returning the `_` variable to its
# previous owner. Returns a reference to the Underscore object.
_.noConflict = ->
root._ = previousUnderscore
this
# Keep the identity function around for default iterators.
_.identity = (value) -> value
# Run a function `n` times.
_.times = (n, iterator, context) ->
iterator.call context, i for i in [0...n]
# Break out of the middle of an iteration.
_.breakLoop = -> throw breaker
# Add your own custom functions to the Underscore object, ensuring that
# they're correctly added to the OOP wrapper as well.
_.mixin = (obj) ->
for name in _.functions(obj)
addToWrapper name, _[name] = obj[name]
# Generate a unique integer id (unique within the entire client session).
# Useful for temporary DOM ids.
idCounter = 0
_.uniqueId = (prefix) ->
(prefix or '') + idCounter++
# By default, Underscore uses **ERB**-style template delimiters, change the
# following template settings to use alternative delimiters.
_.templateSettings = {
start: '<%'
end: '%>'
interpolate: /<%=(.+?)%>/g
}
# JavaScript templating a-la **ERB**, pilfered from John Resig's
# *Secrets of the JavaScript Ninja*, page 83.
# Single-quote fix from Rick Strahl.
# With alterations for arbitrary delimiters, and to preserve whitespace.
_.template = (str, data) ->
c = _.templateSettings
endMatch = new RegExp("'(?=[^"+c.end.substr(0, 1)+"]*"+escapeRegExp(c.end)+")","g")
fn = new Function 'obj',
'var p=[],print=function(){p.push.apply(p,arguments);};' +
'with(obj||{}){p.push(\'' +
str.replace(/\r/g, '\\r')
.replace(/\n/g, '\\n')
.replace(/\t/g, '\\t')
.replace(endMatch,"")
.split("'").join("\\'")
.split("").join("'")
.replace(c.interpolate, "',$1,'")
.split(c.start).join("');")
.split(c.end).join("p.push('") +
"');}return p.join('');"
if data then fn(data) else fn
# Aliases
# -------
_.forEach = _.each
_.foldl = _.inject = _.reduce
_.foldr = _.reduceRight
_.select = _.filter
_.all = _.every
_.any = _.some
_.contains = _.include
_.head = _.first
_.tail = _.rest
_.methods = _.functions
# Setup the OOP Wrapper
# ---------------------
# If Underscore is called as a function, it returns a wrapped object that
# can be used OO-style. This wrapper holds altered versions of all the
# underscore functions. Wrapped objects may be chained.
wrapper = (obj) ->
this._wrapped = obj
this
# Helper function to continue chaining intermediate results.
result = (obj, chain) ->
if chain then _(obj).chain() else obj
# A method to easily add functions to the OOP wrapper.
addToWrapper = (name, func) ->
wrapper.prototype[name] = ->
args = _.toArray arguments
unshift.call args, this._wrapped
result func.apply(_, args), this._chain
# Add all ofthe Underscore functions to the wrapper object.
_.mixin _
# Add all mutator Array functions to the wrapper.
_.each ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], (name) ->
method = Array.prototype[name]
wrapper.prototype[name] = ->
method.apply(this._wrapped, arguments)
result(this._wrapped, this._chain)
# Add all accessor Array functions to the wrapper.
_.each ['concat', 'join', 'slice'], (name) ->
method = Array.prototype[name]
wrapper.prototype[name] = ->
result(method.apply(this._wrapped, arguments), this._chain)
# Start chaining a wrapped Underscore object.
wrapper::chain = ->
this._chain = true
this
# Extracts the result from a wrapped and chained object.
wrapper::value = -> this._wrapped

View file

@ -21,7 +21,7 @@
<%= @ticket.group.name %> -
<%- T(@ticket.ticket_state.name) %> -
<%- T(@ticket.ticket_priority.name) %> -
<%- @ticket.humanTime %>
<span class="humanTimeFromNow" data-time="<%- @ticket.created_at %>">?</span>
</div>
</div>
</div>
@ -48,7 +48,7 @@
<a href="<%= action.href %>" data-type="<%= action.type %>" class="<% if action.class: %><%= action.class %><% end %>"><%= T(action.name) %></a>
<% end %>
<% end %>
- <%- article.humanTime %> <%- T('ago') %>
- <span class="humanTimeFromNow" data-time="<%- article.created_at %>">?</span>
</div>
<% end %>
<% if article.to: %>

View file

@ -1,5 +1,5 @@
<% for ticket in @tickets: %>
<div class="row">
<div class="customer-info"><a href="#ticket/zoom/<%= ticket.id %>" title="<%= ticket.title %>">T:<%= ticket.number %></a> <%= ticket.humanTime %><br/><%= ticket.title %></div>
<div class="customer-info"><a href="#ticket/zoom/<%= ticket.id %>" title="<%= ticket.title %>">T:<%= ticket.number %></a> <span title="<%= ticket.created_at_short %>"><%= ticket.humanTime %></span><br/><%= ticket.title %></div>
</div>
<% end %>