Init version.

This commit is contained in:
Martin Edenhofer 2012-04-10 16:06:46 +02:00
parent 2a5d4954ab
commit a77f3b6435
219 changed files with 30000 additions and 37 deletions

37
Gemfile
View file

@ -1,9 +1,9 @@
source 'https://rubygems.org'
source 'http://rubygems.org'
gem 'rails', '3.2.2'
# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'
#gem 'rails', :git => 'git://github.com/rails/rails.git'
gem 'sqlite3'
@ -12,24 +12,34 @@ gem 'json'
# Gems used only for assets and not required
# in production environments by default.
group :assets do
gem 'sass-rails', '~> 3.2.3'
gem 'coffee-rails', '~> 3.2.1'
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
# gem 'therubyracer'
gem 'uglifier', '>= 1.0.3'
gem 'sass-rails', '~> 3.2.4'
gem 'coffee-rails', '~> 3.2.2'
gem 'uglifier', '>= 1.2.3'
end
gem 'jquery-rails'
# Optional support for eco templates
gem 'eco'
gem "omniauth"
gem "omniauth-twitter"
gem "omniauth-facebook"
gem "omniauth-linkedin"
gem "twitter"
gem "koala"
gem "mail"
gem "mime-types"
gem 'delayed_job_active_record'
gem "daemons"
# To use ActiveModel has_secure_password
# gem 'bcrypt-ruby', '~> 3.0.0'
# To use Jbuilder templates for JSON
# gem 'jbuilder'
# Use unicorn as the app server
# Use unicorn as the web server
# gem 'unicorn'
# Deploy with Capistrano
@ -37,3 +47,4 @@ gem 'jquery-rails'
# To use debugger
# gem 'ruby-debug'

BIN
app/assets/images/close.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View file

@ -0,0 +1,698 @@
class App.Controller extends Spine.Controller
# add @title methode to set title
title: (name) ->
$('html head title').html( Config.product_name + ' - ' + name )
# add @notify methode to create notification
notify: (data) ->
Spine.trigger 'notify', data
# add @navupdate methode to update navigation
navupdate: (url) ->
Spine.trigger 'navupdate', url
# # extend delegateEvents to unbind and undelegate
# delegateEvents: ->
#
# # here unbind and undelegate while @el
# @el.unbind()
# @el.undelegate()
#
# for key, method of @events
# unless typeof(method) is 'function'
# method = @proxy(@[method])
#
# match = key.match(@eventSplitter)
# eventName = match[1]
# selector = match[2]
#
# if selector is ''
# @el.bind(eventName, method)
# else
# @el.delegate(selector, eventName, method)
formGen: (data) ->
form = $('<form>')
fieldset = $('<fieldset>')
fieldset.appendTo(form)
autofocus = 1;
if data.autofocus isnt undefined
autofocus = data.autofocus
attributes = clone( data.model.configure_attributes || [] )
for attribute in attributes
if !attribute.readonly && ( !data.required || data.required && attribute[data.required] )
# set autofocus
if autofocus is 1
attribute.autofocus = 'autofocus'
autofocus = 0
# set required option
if !attribute.null
attribute.required = 'required'
else
attribute.required = ''
# set multible option
if attribute.multiple
attribute.multiple = 'multiple'
else
attribute.multiple = ''
# set autocapitalize option
if attribute.autocapitalize is undefined || attribute.autocapitalize
attribute.autocapitalize = ''
else
attribute.autocapitalize = 'autocapitalize="off"'
# set value
if data.params
if attribute.name of data.params
attribute.value = data.params[attribute.name]
# set default value
else
if 'default' of attribute
@log 'default', attribute.default
attribute.value = attribute.default
else
attribute.value = ''
# add item
item = $( @formGenItem(attribute, data.model.className) )
item.appendTo(fieldset)
# if password, add confirm password item
if attribute.type is 'password'
attribute.display = attribute.display + ' (confirm)'
attribute.name = attribute.name + '_confirm';
item = $( @formGenItem(attribute, data.model.className) )
item.appendTo(fieldset)
# return form
return form.html()
formGenItem: (attribute, classname) ->
# create item id
attribute.id = classname + '_' + attribute.name
# build options list based on config
selection = []
if attribute.options
if attribute.nulloption
attribute.options[''] = '-'
for key of attribute.options
selection.push {
name: attribute.options[key],
value: key,
}
# build options list based on relation
attribute.options = selection || []
if attribute.relation && App[attribute.relation]
attribute.options = []
if attribute.nulloption
attribute.options[''] = '-'
attribute.options.push {
name: '-',
value: '',
}
list = []
if attribute.filter && attribute.filter[attribute.name]
filter = attribute.filter[attribute.name]
# check all records
for record in App[attribute.relation].all()
# check all filter attributes
for key of filter
# check all filter values as array
for value in filter[key]
# if it's matching, use it for selection
if record[key] is value
list.push record
else
list = App[attribute.relation].all()
list.forEach( (item) =>
if item.active
name = '???'
if item.name
name = item.name
else if item.firstname
name = item.firstname
if item.lastname
if name
name = name + ' '
name = name + item.lastname
attribute.options.push {
name: name,
value: item.id,
note: item.note,
}
)
# finde selected/checked item of list
if attribute.options
for record in attribute.options
if typeof attribute.value is 'string' || typeof attribute.value is 'number' || typeof attribute.value is 'boolean'
# if name or value is matching
if record.value.toString() is attribute.value.toString() || record.name.toString() is attribute.value.toString()
record.selected = 'selected'
record.checked = 'checked'
# 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) )
record.selected = 'selected'
record.checked = 'checked'
# boolean
if attribute.tag is 'boolean'
# build options list
attribute.options = [
{ name: 'active', value: true }
{ name: 'inactive', value: false }
] || []
# finde selected item of list
for record in attribute.options
if record.value is attribute.value
record.selected = 'selected'
# return item
item = App.view('generic/select')( attribute: attribute )
# select
else if attribute.tag is 'select'
item = App.view('generic/select')( attribute: attribute )
# checkbox
else if attribute.tag is 'checkbox'
item = App.view('generic/checkbox')( attribute: attribute )
# radio
else if attribute.tag is 'radio'
item = App.view('generic/radio')( attribute: attribute )
# textarea
else if attribute.tag is 'textarea'
item = App.view('generic/textarea')( attribute: attribute )
# autocompletion
else if attribute.tag is 'autocompletion'
item = App.view('generic/autocompletion')( attribute: attribute )
a = ->
# if attribute.relation && App[attribute.relation]
# @log '1312312333333333333', App[attribute.relation]
# @log '1231231231', '#' + attribute.id + '_autocompletion'
@local_attribute = '#' + attribute.id
@local_attribute_full = '#' + attribute.id + '_autocompletion'
@callback = attribute.callback
b = (event, key) =>
# @log 'zzzz', event, item, key, @local_attribute
$(@local_attribute).val(key)
if @callback
@callback( user_id: key )
###
$(@local_attribute_full).tagsInput(
autocomplete_url: '/user_search',
height: '30px',
width: '530px',
auto: {
source: '/user_search',
minLength: 2,
select: ( event, ui ) =>
@log 'selected', event, ui
b(event, ui.item.id)
}
)
###
$(@local_attribute_full).autocomplete(
source: '/user_search',
minLength: 2,
select: ( event, ui ) =>
@log 'selected', event, ui
b(event, ui.item.id)
)
@delay(a, 800)
# input
else
item = App.view('generic/input')( attribute: attribute )
return App.view('generic/attribute')(
attribute: attribute,
item: item,
)
# get all params of the form
formParam: (form, errors) ->
param = {}
# find form based on sub elements
if $(form).children()[0]
form = $(form).children().parents('form')
# find form based on parents next <form>
else if $(form).parents('form')[0]
form = $(form).parents('form')
# find form based on parents next <form>, not really good!
else if $(form).parents().find('form')[0]
form = $(form).parents().find('form')
else
@log 'ERROR, no form found!', form
for key in form.serializeArray()
if param[key.name]
if typeof param[key.name] is 'string'
param[key.name] = [ param[key.name], key.value]
else
param[key.name].push key.value
else
param[key.name] = key.value
@log 'formParam', form, param
return param
formDisable: (form) ->
@log 'disable...', $(form.target).parent()
$(form.target).parent().find('[type="submit"]').attr('disabled', true)
$(form.target).parent().find('[type="reset"]').attr('disabled', true)
formEnable: (form) ->
@log 'enable...', $(form).parent()
$(form).parent().find('[type="submit"]').attr('disabled', false)
$(form).parent().find('[type="reset"]').attr('disabled', false)
table: (data) ->
overview = data.overview || data.model.configure_overview || []
attributes = data.attributes || data.model.configure_attributes || []
# define normal header
header = []
for row in overview
for attribute in attributes
if row is attribute.name
header.push(attribute.display)
else
rowWithoutId = row + '_id'
if rowWithoutId is attribute.name
header.push(attribute.display)
data_types = []
for row in overview
data_types.push {
name: row,
link: 1,
}
# extended table format
if data.overview_extended
header = []
for row in data.overview_extended
for attribute in attributes
if row.name is attribute.name
header.push(attribute.display)
else
rowWithoutId = row.name + '_id'
if rowWithoutId is attribute.name
header.push(attribute.display)
data_types = data.overview_extended
# generate content data
objects = clone( data.objects )
for object in objects
for row in data_types
# check if data is a object
if typeof object[row.name] is 'object'
if !object[row.name]
object[row.name] = {
name: '-',
}
# 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 it isnt a object, create one
else if typeof object[row.name] isnt 'object'
object[row.name] = {
name: object[row.name],
}
# fallback if it's something else
else
object[row.name] = {
name: '????',
}
# execute callback on content
if row.callback
object[row.name]['name'] = row.callback(object[row.name]['name'])
# @log 'table', 'header', header, 'overview', data_types, 'objects', objects
table = App.view('generic/table')(
header: header,
overview: data_types,
objects: objects,
checkbox: data.checkbox,
)
# @log 'ttt', $(table).find('span')
# $(table).find('span').bind('click', ->
# console.log('----------click---------')
# )
# convert to jquery object
table = $(table)
# enable checkbox bulk selection
if data.checkbox
table.delegate('[name="bulk_all"]', 'click', (e) ->
if $(e.target).attr('checked')
$(e.target).parents().find('[name="bulk"]').attr('checked', true);
else
$(e.target).parents().find('[name="bulk"]').attr('checked', false);
)
return table
ticketTableAttributes: (attributes) =>
all_attributes = [
{ name: 'number', link: true },
{ name: 'title', link: true },
{ name: 'customer', class: 'user-data', data: { id: true } },
{ name: 'ticket_state' },
{ name: 'ticket_priority' },
{ 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 },
]
shown_all_attributes = []
for all_attribute in all_attributes
for attribute in attributes
if all_attribute['name'] is attribute
shown_all_attributes.push all_attribute
break
return shown_all_attributes
validateForm: (data) ->
# remove all errors
$(data.form).parents().find('.error').removeClass('error')
$(data.form).parents().find('.help-inline').html('')
# show new errors
for key, msg of data.errors
$(data.form).parents().find('[name*="' + key + '"]').parents('div .control-group').addClass('error')
$(data.form).parents().find('[name*="' + key + '"]').parent().find('.help-inline').html(msg);
# set autofocus
$(data.form).parents().find('.error').find('input, textarea').first().focus()
# # enable form again
# if $(data.form).parents().find('.error').html()
# @formEnable(data.form)
# redirectToLogin: (data) ->
#
# human readable file size
humanFileSize: (size) =>
if size > ( 1024 * 1024 )
size = Math.round( size / ( 1024 * 1024 ) ) + ' MBytes'
else if size > 1024
size = Math.round( size / 1024 ) + ' KBytes'
else
size = size + ' Bytes'
return size
# human readable time
humanTime: (time) =>
current = new Date()
created = new Date(time)
diff = (current - created) / 1000
if diff >= 86400
unit = Math.round( (diff / 86400) )
if unit > 1
return unit + ' days'
else
return unit + ' day'
if diff >= 3600
unit = Math.round( (diff / 3600) )
if unit > 1
return unit + ' hours'
else
return unit + ' hour'
if diff <= 3600
unit = Math.round( (diff / 60) )
if unit > 1
return unit + ' minutes'
else
return unit + ' minute'
userInfo: (data) =>
# start customer info controller
new App.UserInfo(
el: data.el || $('#customer_info'),
user_id: data.user_id,
)
authenticate: ->
console.log 'authenticate', window.Session
return true if window.Session['id']
# redirect to login
@navigate '#login'
return false
clone = (obj) ->
if not obj? or typeof obj isnt 'object'
return obj
newInstance = new obj.constructor()
for key of obj
newInstance[key] = clone obj[key]
return newInstance
userPopups: (position = 'right') ->
# show user popup
$('.user-data').popover(
delay: { show: 500, hide: 1200 },
# placement: 'bottom',
placement: position,
title: (e) =>
user_id = $(e).data('id')
user = App.User.find(user_id)
(user.firstname || '') + ' ' + (user.lastname || '')
content: (e) =>
user_id = $(e).data('id')
user = App.User.find(user_id)
# get display data
data = []
for item in App.User.configure_attributes
if user[item.name]
if item.name isnt 'firstname'
if item.name isnt 'lastname'
if item.info #&& ( @user[item.name] || item.name isnt 'note' )
data.push item
# insert data
App.view('user_info_small')(
user: user,
data: data,
)
)
userTicketPopups: (data) ->
# get data
@tickets = {}
ajax = new App.Ajax
ajax.ajax(
type: 'GET',
url: '/ticket_customer',
data: {
customer_id: data.user_id,
}
processData: true,
success: (data, status, xhr) =>
@tickets = data.tickets
)
if !data.position
data.position = 'left'
# show user popup
$(data.selector).popover(
delay: { show: 500, hide: 5200 },
placement: data.position,
title: (e) =>
$(e).find('[title="*"]').val()
content: (e) =>
type = $(e).filter('[data-type]').data('type')
data = @tickets[type] || []
for ticket in data
# set human time
ticket.humanTime = @humanTime(ticket.created_at)
# insert data
App.view('user_ticket_info_small')(
tickets: data,
)
)
loadCollection: (params) ->
# users
if params.type == 'User'
for user of params.data
if user && !user.image
user.image = 'http://placehold.it/48x48'
App.User.refresh( params.data[user], options: { clear: true } )
# tickets
else if params.type == 'Ticket'
for ticket in params.data
# 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 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 )
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 )
App.History.refresh( histroy, options: { clear: true } )
# all the rest
else
for object in params.data
App[params.type].refresh( object, options: { clear: true } )
class App.ControllerModal extends App.Controller
className: 'modal hide fade',
tag: 'div',
events:
'submit form': 'submit',
'click .submit': 'submit',
'click .cancel': 'modalHide',
'click .close': 'modalHide',
constructor: (options) ->
# do not use @el, because it's inserted by js
if options
delete options.el
# callbacks
# @callback = {}
# if options.success
# @callback.success = options.success
# if options.error
# @callback.error = options.error
super(options)
modalShow: (params) =>
@el.modal({
backdrop: true,
keyboard: true,
show: true
})
@el.bind('hidden', =>
# navigate back to home page
if @pageData && @pageData.home
@navigate @pageData.home
# navigate back
if params && params.navigateBack
window.history.back()
# remove modal from dom
$('.modal').remove();
)
modalHide: (e) =>
if e
e.preventDefault()
@el.modal('hide')

View file

@ -0,0 +1,230 @@
$ = jQuery.sub()
$.fn.item = (genericObject) ->
elementID = $(@).data('id')
elementID or= $(@).parents('[data-id]').data('id')
genericObject.find(elementID)
class App.ControllerGenericNew extends App.ControllerModal
constructor: (params) ->
super
@render()
render: ->
@log 'ren new', @el
@html App.view('generic/admin/new')(
form: @formGen( model: @genericObject ),
head: 'New ' + @pageData.object
)
@modalShow()
submit: (e) ->
@log 'submit'
e.preventDefault()
params = @formParam(e.target)
###
for num in [1..199]
user = new User
params.login = 'login_c' + num
user.updateAttributes(params)
return false
###
object = new @genericObject
object.load(params)
# validate
errors = object.validate( form: true )
if errors
@log 'error new', errors
@validateForm( form: e.target, errors: errors )
return false
# save object
object.save(
success: =>
@modalHide()
error: =>
@log 'errors'
@modalHide()
)
class App.ControllerGenericEdit extends App.ControllerModal
constructor: (params) ->
super
@log 'ControllerGenericEditWindow', params
# fetch item on demand
if @genericObject.exists(params.id)
@item = @genericObject.find(params.id)
@render()
else
@genericObject.bind 'refresh', =>
@log 'changed....'
@item = @genericObject.find(params.id)
@render()
@genericObject.unbind 'refresh'
@genericObject.fetch( id: params.id)
render: ->
@html App.view('generic/admin/edit')(
form: @formGen( model: @genericObject, params: @item ),
head: 'Edit ' + @pageData.object
)
@modalShow()
submit: (e) ->
e.preventDefault()
params = @formParam(e.target)
@item.load(params)
# validate
errors = @item.validate( form: true )
if errors
@log 'error new', errors
@validateForm( form: e.target, errors: errors )
return false
@log 'save....'
# save object
@item.save(
success: =>
@modalHide()
error: =>
@log 'errors'
@modalHide()
)
class App.ControllerGenericIndex extends App.Controller
events:
'click [data-type=edit]': 'edit'
'click [data-type=destroy]': 'destroy'
'click [data-type=new]': 'new'
constructor: ->
super
# set controller to active
Config['ActiveController'] = @pageData.navupdate
# set title
@title @pageData.title
# set nav bar
@navupdate @pageData.navupdate
# bind render after a change is done
@genericObject.bind 'refresh change', @render
@genericObject.bind 'ajaxError', (rec, msg) =>
@log 'ajax notice', msg.status
if msg.status is 401
@log 'ajax error', rec, msg, msg.status
# @navigate @pageData.navupdate
# alert('relogin')
@navigate 'login'
# execute fetch, if needed
if !@genericObject.count() || true
# if !@genericObject.count()
# prerender without content
@render()
# fetch all
@genericObject.fetch()
else
@render()
render: =>
return if Config['ActiveController'] isnt @pageData.navupdate
objects = @genericObject.all()
# remove ignored items from collection
if @ignoreObjectIDs
objects = _.filter(objects, (item) ->
return if item.id is 1
return item
)
@html App.view('generic/admin/index')(
head: @pageData.objects,
notes: @pageData.notes,
buttons: @pageData.buttons,
menus: @pageData.menus,
)
# append content table
table = @table(
model: @genericObject,
objects: objects,
)
@el.find('.table-overview').append(table)
edit: (e) =>
e.preventDefault()
item = $(e.target).item(@genericObject)
new App.ControllerGenericEdit(
id: item.id,
pageData: @pageData,
genericObject: @genericObject
)
destroy: (e) ->
item = $(e.target).item(@genericObject)
item.destroy() if confirm('Sure?')
new: (e) ->
e.preventDefault()
new App.ControllerGenericNew(
pageData: @pageData,
genericObject: @genericObject
)
class App.ControllerLevel2 extends App.Controller
events:
'click [data-toggle="tabnav"]': 'toggle',
constructor: ->
super
return if !@authenticate()
render: ->
@log 'ttt', @target, @
# set title
@title @page.title
@navupdate @page.nav
@html App.view('generic/admin_level2/index')(
page: @page,
menus: @menu,
type: @type,
target: @target,
)
for menu in @menu
@el.find('.nav-tab-content').append('<div class="tabbable" id="' + menu.target + '">' + menu.name + '</div>')
if menu.controller
params = menu.params || {}
params.el = @el.find( '#' + menu.target )
new menu.controller( params )
@el.find('.tabbable').addClass('hide')
if @target
@el.find( '#' + @target ).removeClass('hide')
else
@el.find('.tabbable:first').removeClass('hide')
@el.find('[data-toggle="tabnav"]:first').addClass('active')
toggle: (e) ->
return true if @toggleable is false
e.preventDefault()
target = $(e.target).data('target')
$(e.target).parents('ul').find('li').removeClass('active')
$(e.target).parents('li').addClass('active')
@el.find('.tabbable').addClass('hide')
@el.find('#' + target).removeClass('hide')
# window.scrollTo(0,0)

View file

@ -0,0 +1,18 @@
$ = jQuery.sub()
class App.ChannelChat extends App.Controller
events:
'click [data-toggle="tabnav"]': 'toggle',
constructor: ->
super
# render page
@render()
render: ->
@html App.view('channel/chat')(
head: 'some header'
)

View file

@ -0,0 +1,18 @@
$ = jQuery.sub()
class App.ChannelEmail extends App.Controller
events:
'click [data-toggle="tabnav"]': 'toggle',
constructor: ->
super
# render page
@render()
render: ->
@html App.view('channel/email')(
head: 'some header'
)

View file

@ -0,0 +1,18 @@
$ = jQuery.sub()
class App.ChannelFacebook extends App.Controller
events:
'click [data-toggle="tabnav"]': 'toggle',
constructor: ->
super
# render page
@render()
render: ->
@html App.view('channel/facebook')(
head: 'some header'
)

View file

@ -0,0 +1,18 @@
$ = jQuery.sub()
class App.ChannelTwitter extends App.Controller
events:
'click [data-toggle="tabnav"]': 'toggle',
constructor: ->
super
# render page
@render()
render: ->
@html App.view('channel/twitter')(
head: 'some header'
)

View file

@ -0,0 +1,18 @@
$ = jQuery.sub()
class App.ChannelWeb extends App.Controller
events:
'click [data-toggle="tabnav"]': 'toggle',
constructor: ->
super
# render page
@render()
render: ->
@html App.view('channel/web')(
head: 'some header'
)

View file

@ -0,0 +1,61 @@
$ = jQuery.sub()
class App.DashboardActivityStream extends App.Controller
events:
'click [data-type=edit]': 'zoom'
constructor: ->
super
# @log 'aaaa', @el
@items = []
# get data
@ajax = new App.Ajax
@ajax.ajax(
type: 'GET',
url: '/activity_stream',
data: {
limit: 10,
}
processData: true,
# data: JSON.stringify( view: @view ),
success: (data, status, xhr) =>
@items = data.activity_stream
# load user collection
@loadCollection( type: 'User', data: data.users )
# load ticket collection
@loadCollection( type: 'Ticket', data: data.tickets )
@render()
)
render: ->
# load user data
for item in @items
item.created_by = App.User.find(item.created_by_id)
# load ticket data
for item in @items
item.ticket = App.Ticket.find(item.o_id)
html = App.view('dashboard/activity_stream')(
head: 'Activity Stream',
items: @items
)
html = $(html)
@html html
# start user popups
@userPopups('left')
zoom: (e) =>
e.preventDefault()
id = $(e.target).parents('[data-id]').data('id')
@log 'goto zoom!'
@navigate 'ticket/zoom/' + id

View file

@ -0,0 +1,63 @@
$ = jQuery.sub()
class App.DashboardRecentViewed extends App.Controller
events:
'click [data-type=edit]': 'zoom'
constructor: ->
super
# @log 'aaaa', @el
@items = []
# get data
@ajax = new App.Ajax
@ajax.ajax(
type: 'GET',
url: '/recent_viewed',
data: {
limit: 5,
}
processData: true,
# data: JSON.stringify( view: @view ),
success: (data, status, xhr) =>
@items = data.recent_viewed
# load user collection
@loadCollection( type: 'User', data: data.users )
# load ticket collection
@loadCollection( type: 'Ticket', data: data.tickets )
@render()
)
render: ->
# load user data
for item in @items
# @log 'load', item.created_by_id
item.created_by = App.User.find(item.created_by_id)
# load ticket data
for item in @items
# @log 'load', item.o_id
item.ticket = App.Ticket.find(item.o_id)
html = App.view('dashboard/recent_viewed')(
head: 'Recent Viewed',
items: @items
)
html = $(html)
@html html
# start user popups
@userPopups('left')
zoom: (e) =>
e.preventDefault()
id = $(e.target).parents('[data-id]').data('id')
@log 'goto zoom!'
@navigate 'ticket/zoom/' + id

View file

@ -0,0 +1,235 @@
$ = jQuery.sub()
class App.DashboardTicket extends App.Controller
events:
'click [data-type=edit]': 'zoom'
'click [data-type=settings]': 'settings'
'click [data-type=page]': 'page'
constructor: ->
super
@tickets = []
@tickets_count = 0
@start_page = 1
@navupdate '#'
@fetch()
fetch: ->
# get data
@ajax = new App.Ajax
@ajax.ajax(
type: 'GET',
url: '/ticket_overviews',
data: {
view: @view,
view_mode: 'd',
start_page: @start_page,
}
processData: true,
# data: JSON.stringify( view: @view ),
success: (data, status, xhr) =>
# get meta data
@overview = data.overview
App.Overview.refresh( @overview, options: { clear: true } )
App.Overview.unbind('local:rerender')
App.Overview.bind 'local:rerender', (record) =>
@log 'rerender...', record
@render()
App.Overview.unbind('local:refetch')
App.Overview.bind 'local:refetch', (record) =>
@log 'refetch...', record
@fetch()
# load user collection
@loadCollection( type: 'User', data: data.users )
# load ticket collection
@loadCollection( type: 'Ticket', data: data.tickets )
@tickets = data.tickets
@tickets_count = data.tickets_count
@render()
)
render: ->
pages_total = parseInt( ( @tickets_count / @overview.view.d.per_page ) + 0.99999 ) || 1
html = App.view('dashboard/ticket')(
overview: @overview,
pages_total: pages_total,
start_page: @start_page,
)
html = $(html)
html.find('li').removeClass('active')
html.find("[data-id=\"#{@start_page}\"]").parents('li').addClass('active')
shown_all_attributes = @ticketTableAttributes( App.Overview.find(@overview.id).view.d.overview )
table = @table(
overview_extended: shown_all_attributes,
model: App.Ticket,
objects: @tickets,
checkbox: false,
)
if _.isEmpty(@tickets)
table = ''
# table = '-none-'
# append content table
html.find('.table-overview').append(table)
@html html
# start user popups
@userPopups()
zoom: (e) =>
e.preventDefault()
id = $(e.target).parents('[data-id]').data('id')
@log 'goto zoom!'
@navigate 'ticket/zoom/' + id
settings: (e) =>
e.preventDefault()
new Settings(
overview: App.Overview.find(@overview.id)
)
page: (e) =>
e.preventDefault()
id = $(e.target).data('id')
@start_page = id
@fetch()
class Settings extends App.ControllerModal
constructor: ->
super
@render()
render: ->
@html App.view('dashboard/ticket_settings')(
overview: @overview,
)
@configure_attributes_article = [
# { name: 'from', display: 'From', tag: 'input', type: 'text', limit: 100, null: false, class: 'span8', },
# { name: 'to', display: 'To', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide' },
# { name: 'ticket_article_type_id', display: 'Type', tag: 'select', multiple: false, null: true, relation: 'TicketArticleType', default: '9', class: 'medium', item_class: 'keepleft' },
# { name: 'internal', display: 'Visability', tag: 'radio', default: false, null: true, options: { true: 'internal', false: 'public' }, class: 'medium', item_class: 'keepleft' },
{
name: 'per_page',
display: 'Items per page',
tag: 'select',
multiple: false,
null: false,
default: @overview.view.d.per_page,
options: {
5: 5,
10: 10,
15: 15,
20: 20,
},
class: 'medium',
# item_class: 'keepleft',
},
{
name: 'attributes',
display: 'Attributes',
tag: 'checkbox',
default: @overview.view.d.overview,
null: false,
options: {
number: 'Number',
title: 'Title',
customer: 'Customer',
ticket_state: 'State',
ticket_priority: 'Priority',
group: 'Group',
owner: 'Owner',
created_at: 'Alter',
last_contact: 'Last Contact',
last_contact_agent: 'Last Contact Agent',
last_contact_customer: 'Last Contact Customer',
first_response: 'First Response',
close_time: 'Close Time',
},
class: 'medium',
# item_class: 'keepleft',
},
{
name: 'order_by',
display: 'Order',
tag: 'select',
default: @overview.order.by,
null: false,
options: {
number: 'Number',
title: 'Title',
customer: 'Customer',
ticket_state: 'State',
ticket_priority: 'Priority',
group: 'Group',
owner: 'Owner',
created_at: 'Alter',
last_contact: 'Last Contact',
last_contact_agent: 'Last Contact Agent',
last_contact_customer: 'Last Contact Customer',
first_response: 'First Response',
close_time: 'Close Time',
},
class: 'medium',
},
{
name: 'order_by_direction',
display: 'Direction',
tag: 'select',
default: @overview.order.direction,
null: false,
options: {
ASC: 'up',
DESC: 'down',
},
class: 'medium',
},
]
form = @formGen( model: { configure_attributes: @configure_attributes_article } )
@el.find('.setting').append(form)
@modalShow()
submit: (e) =>
e.preventDefault()
params = @formParam(e.target)
# check if refetch is needed
@reload_needed = 0
if @overview.view['d']['per_page'] isnt params['per_page']
@overview.view['d']['per_page'] = params['per_page']
@reload_needed = 1
if @overview.order['by'] isnt params['order_by']
@overview.order['by'] = params['order_by']
@reload_needed = 1
if @overview.order['direction'] isnt params['order_by_direction']
@overview.order['direction'] = params['order_by_direction']
@reload_needed = 1
@overview.view['d']['overview'] = params['attributes']
@overview.save(
success: =>
if @reload_needed
@overview.trigger('local:refetch')
else
@overview.trigger('local:rerender')
)
@modalHide()

View file

@ -0,0 +1,70 @@
$ = jQuery.sub()
class App.SettingsArea extends App.Controller
constructor: ->
super
# check authentication
return if !@authenticate()
App.Setting.bind 'refresh change', @render
App.Setting.fetch()
render: =>
settings = App.Setting.all()
html = $('<div></div>')
for setting in settings
if setting.area is @area
item = new App.SettingsAreaItem( setting: setting )
html.append( item.el )
@html html
class App.SettingsAreaItem extends App.Controller
events:
'submit form': 'update',
constructor: ->
super
@render()
render: =>
# defaults
for item in @setting.options['form']
if typeof @setting.state.value is 'object'
item['default'] = @setting.state.value[item.name]
else
item['default'] = @setting.state.value
# form
@configure_attributes = @setting.options['form']
form = @formGen( model: { configure_attributes: @configure_attributes, className: '' }, autofocus: false )
# item
@html App.view('settings/item')(
setting: @setting,
form: form,
)
update: (e) =>
e.preventDefault()
params = @formParam(e.target)
@log 'submit', @setting, params, e.target
if typeof @setting.state.value is 'object'
state = {
value: params
}
else
state = {
value: params[@setting.name]
}
@setting['state'] = state
@setting.save(
success: =>
# login check
auth = new App.Auth
auth.loginCheck()
)

View file

@ -0,0 +1,185 @@
$ = jQuery.sub()
class Index extends App.Controller
events:
'click .customer_new': 'user_new'
'submit form': 'submit',
'click .submit': 'submit',
'click .cancel': 'cancel',
constructor: ->
super
# check authentication
return if !@authenticate()
# set title
@title 'New Ticket'
# @render()
@fetch()
@navupdate '#ticket_create'
@edit_form = undefined
fetch: () ->
# get data
@ajax = new App.Ajax
@ajax.ajax(
type: 'GET',
url: '/ticket_create',
data: {
# view: @view
}
processData: true,
success: (data, status, xhr) =>
# get edit form attributes
@edit_form = data.edit_form
# load user collection
@loadCollection( type: 'User', data: data.users )
# render page
@render()
)
render: ->
configure_attributes = [
{ name: 'customer_id', display: 'From', tag: 'autocompletion', type: 'text', limit: 100, null: false, relation: 'User', class: 'span7', autocapitalize: false, help: 'Select the customer of the Ticket or create one.', link: '<a href="" class="customer_new">&raquo;</a>', callback: @userInfo },
{ name: 'group_id', display: 'Group', tag: 'select', multiple: false, null: false, filter: @edit_form, nulloption: true, relation: 'Group', class: 'span7', },
{ name: 'owner_id', display: 'Owner', tag: 'select', multiple: false, null: true, filter: @edit_form, nulloption: true, relation: 'User', class: 'span7', },
{ name: 'subject', display: 'Subject', tag: 'input', type: 'text', limit: 100, null: false, class: 'span7', },
{ name: 'body', display: 'Text', tag: 'textarea', rows: 6, limit: 100, null: false, class: 'span7', },
{ name: 'ticket_state_id', display: 'State', tag: 'select', multiple: false, null: false, filter: @edit_form, relation: 'TicketState', default: 'new', class: 'medium' },
{ name: 'ticket_priority_id', display: 'Priority', tag: 'select', multiple: false, null: false, filter: @edit_form, relation: 'TicketPriority', default: '2 normal', class: 'medium' },
]
@html App.view('agent_ticket_create')(
head: 'New Ticket',
form: @formGen( model: { configure_attributes: configure_attributes, className: 'create' } ),
)
# @modalShow(
# navigateBack: true
# )
user_new: (e) =>
e.preventDefault()
new UserNew()
cancel: ->
@render()
submit: (e) ->
e.preventDefault()
# get params
params = @formParam(e.target)
# fillup params
if !params.title
params.title = params.subject
# create ticket
object = new App.Ticket
@log 'updateAttributes', params
object.load(params)
# validate form
errors = object.validate()
# show errors in form
if errors
@log 'error new', errors
@validateForm( form: e.target, errors: errors )
# save ticket, create article
else
# disable form
@formDisable(e)
object.save(
success: (r) =>
# find sender_id
sender = App.TicketArticleSender.findByAttribute("name", "Customer")
type = App.TicketArticleType.findByAttribute("name", "phone")
# create article
article = new App.TicketArticle
article.load(
from: 'some guy',
to: 'some group',
subject: params.subject,
body: params.body,
ticket_id: r.id,
ticket_article_type_id: type.id,
ticket_article_sender_id: sender.id,
)
article.save()
# console.log('params', params)
# notify UI
@notify
type: 'success',
msg: 'Ticket ' + r.number + ' created!'
# create new create screen
@render()
error: =>
@log 'save failed!'
)
class UserNew extends App.ControllerModal
constructor: ->
super
@render()
render: ->
@html App.view('agent_user_create')(
head: 'New User',
form: @formGen( model: App.User, required: 'quick' ),
)
@modalShow()
submit: (e) ->
@log 'submit'
e.preventDefault()
params = @formParam(e.target)
# if no login is given, use emails as fallback
if !params.login && params.email
params.login = params.email
user = new App.User
# find role_id
role = App.Role.findByAttribute("name", "Customer")
params.role_ids = role.id
@log 'updateAttributes', params
user.load(params)
errors = user.validate()
if errors
@log 'error new', errors
@validateForm( form: e.target, errors: errors )
return
# save user
user.save(
success: (r) =>
@modalHide()
$('#create_customer_id').val(r.id)
$('#create_customer_id_autocompletion').val(r.firstname)
# start customer info controller
@userInfo( user_id: r.id )
error: =>
@modalHide()
)
Config.Routes['ticket_create'] = Index

View file

@ -0,0 +1,399 @@
$ = jQuery.sub()
class Index extends App.Controller
events:
'click [data-type=edit]': 'zoom'
'click [data-type=settings]': 'settings'
'click [data-type=view-mode]': 'view_mode'
'click [data-type=page]': 'page'
constructor: ->
super
# check authentication
return if !@authenticate()
@log 'view:', @view
@view_mode = localStorage.getItem( "mode:#{@view}" ) || 's'
# set title
@title ''
@navupdate '#ticket_view'
@tickets = []
@tickets_count = 0
@start_page = 1
@meta = {}
@bulk = {}
# @render()
@fetch()
fetch: ->
# get data
@ajax = new App.Ajax
@ajax.ajax(
type: 'GET',
url: '/ticket_overviews',
data: {
view: @view,
view_mode: @view_mode,
start_page: @start_page,
}
processData: true,
success: (data, status, xhr) =>
# get meta data
@overview = data.overview
App.Overview.refresh( @overview, options: { clear: true } )
App.Overview.unbind('local:rerender')
App.Overview.bind 'local:rerender', (record) =>
@log 'rerender...', record
@render()
App.Overview.unbind('local:refetch')
App.Overview.bind 'local:refetch', (record) =>
@log 'refetch...', record
@fetch()
# set page title
@title @overview.meta.name
# load user collection
@loadCollection( type: 'User', data: data.users )
# load ticket collection
@loadCollection( type: 'Ticket', data: data.tickets )
# remember ticket order
@tickets = data.tickets
# remember ticket count
@tickets_count = data.tickets_count
# remeber bulk attributes
@bulk = data.bulk
# render page
@render()
)
# refresh/load default collections
# for key, value of data.default_collections
# App[key].refresh( value, options: { clear: true } )
render: ->
pages_total = parseInt( ( @tickets_count / @overview.view[@view_mode].per_page ) + 0.99999 ) || 1
# render init page
view_modes = [
{
name: 'S',
type: 's',
class: 'active' if @view_mode is 's',
},
{
name: 'M',
type: 'm',
class: 'active' if @view_mode is 'm',
}
]
html = App.view('agent_ticket_view')(
overview: @overview,
view_modes: view_modes,
pages_total: pages_total,
start_page: @start_page,
checkbox: true,
)
html = $(html)
# html.find('li').removeClass('active')
html.find("[data-id=\"#{@start_page}\"]").parents('li').addClass('active')
@html html
# create table/overview
table = ''
if @view_mode is 'm'
table = App.view('agent_ticket_view/detail')(
overview: @overview,
objects: @tickets,
checkbox: true
)
table = $(table)
table.delegate('[name="bulk_all"]', 'click', (e) ->
if $(e.target).attr('checked')
$(e.target).parents().find('[name="bulk"]').attr('checked', true);
else
$(e.target).parents().find('[name="bulk"]').attr('checked', false);
)
else
shown_all_attributes = @ticketTableAttributes( App.Overview.find(@overview.id).view.s.overview )
table = @table(
overview_extended: shown_all_attributes,
model: App.Ticket,
objects: @tickets,
checkbox: true,
)
# append content table
@el.find('.table-overview').append(table)
# start user popups
@userPopups()
# start bulk action observ
@el.find('.bulk-action').append( @bulk_form() )
# show/hide bulk action
@el.find('.table-overview').delegate('[name="bulk"], [name="bulk_all"]', 'click', (e) =>
if @el.find('.table-overview').find('[name="bulk"]:checked').length == 0
# hide
@el.find('.bulk-action').addClass('hide')
else
# show
@el.find('.bulk-action').removeClass('hide')
)
page: (e) =>
e.preventDefault()
id = $(e.target).data('id')
@start_page = id
@fetch()
view_mode: (e) =>
e.preventDefault()
@start_page = 1
id = $(e.target).data('mode')
@view_mode = id
localStorage.setItem( "mode:#{@view}", id )
@fetch()
@render()
bulk_form: =>
@configure_attributes_ticket = [
{ name: 'ticket_state_id', display: 'State', tag: 'select', multiple: false, null: true, relation: 'TicketState', nulloption: true, default: '', class: 'span2', item_class: 'keepleft' },
{ name: 'ticket_priority_id', display: 'Priority', tag: 'select', multiple: false, null: true, relation: 'TicketPriority', nulloption: true, default: '', class: 'span2', item_class: 'keepleft' },
{ name: 'group_id', display: 'Group', tag: 'select', multiple: false, null: true, relation: 'Group', nulloption: true, class: 'span2', item_class: 'keepleft' },
{ name: 'owner_id', display: 'Owner', tag: 'select', multiple: false, null: true, relation: 'User', filter: @bulk, nulloption: true, class: 'span2', item_class: 'keepleft' },
]
form_ticket = @formGen( model: { configure_attributes: @configure_attributes_ticket, className: 'create' } )
@configure_attributes_article = [
# { name: 'from', display: 'From', tag: 'input', type: 'text', limit: 100, null: false, class: 'span8', },
{ name: 'to', display: 'To', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide' },
{ name: 'cc', display: 'Cc', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide' },
{ name: 'subject', display: 'Subject', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide' },
{ name: 'in_reply_to', display: 'In Reply to', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide' },
{ name: 'body', display: 'Text', tag: 'textarea', rows: 4, limit: 100, null: true, class: 'span7', },
{ name: 'ticket_article_type_id', display: 'Type', tag: 'select', multiple: false, null: true, relation: 'TicketArticleType', default: '9', class: 'medium', item_class: 'keepleft' },
{ name: 'internal', display: 'Visability', tag: 'radio', default: false, null: true, options: { true: 'internal', false: 'public' }, class: 'medium', item_class: 'keepleft' },
# { name: 'ticket_article_sender_id', display: 'Sender', tag: 'select', multiple: false, null: true, relation: 'TicketArticleSender', default: '', class: 'medium' },
]
form_article = @formGen( model: { configure_attributes: @configure_attributes_article } )
# render init page
html = App.view('agent_ticket_view/bulk')(
meta: @overview.meta,
checkbox: true
form_ticket: form_ticket,
# form_article: form_article,
)
html = $(html)
# html.delegate('.bulk-action-form', 'submit', (e) =>
html.bind('submit', (e) =>
e.preventDefault()
@bulk_submit(e)
)
return html
bulk_submit: (e) =>
@bulk_count = @el.find('.table-overview').find('[name="bulk"]:checked').length
@bulk_count_index = 0
@el.find('.table-overview').find('[name="bulk"]:checked').each( (index, element) =>
@log '@bulk_count_index', @bulk_count, @bulk_count_index
ticket_id = $(element).val()
ticket = App.Ticket.find(ticket_id)
params = @formParam(e.target)
# update ticket
ticket_update = {}
for item of params
if params[item] != ''
ticket_update[item] = params[item]
# @log 'update', params, ticket_update, ticket
ticket.load(ticket_update)
ticket.save(
success: (r) =>
@bulk_count_index++
# refresh view after all tickets are proceeded
if @bulk_count_index == @bulk_count
@tickets = []
# rebuild navbar with updated ticket count of overviews
Spine.trigger 'navupdate_remote'
# fetch overview data again
@fetch()
)
)
zoom: (e) =>
e.preventDefault()
id = $(e.target).parents('[data-id]').data('id')
@navigate 'ticket/zoom/' + id
settings: (e) =>
e.preventDefault()
new Settings(
overview: App.Overview.find(@overview.id),
view_mode: @view_mode,
)
class Settings extends App.ControllerModal
constructor: ->
super
@render()
render: ->
@html App.view('dashboard/ticket_settings')(
overview: @overview,
)
@configure_attributes_article = [
# { name: 'from', display: 'From', tag: 'input', type: 'text', limit: 100, null: false, class: 'span8', },
# { name: 'to', display: 'To', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide' },
# { name: 'ticket_article_type_id', display: 'Type', tag: 'select', multiple: false, null: true, relation: 'TicketArticleType', default: '9', class: 'medium', item_class: 'keepleft' },
# { name: 'internal', display: 'Visability', tag: 'radio', default: false, null: true, options: { true: 'internal', false: 'public' }, class: 'medium', item_class: 'keepleft' },
{
name: 'per_page',
display: 'Items per page',
tag: 'select',
multiple: false,
null: false,
default: @overview.view[@view_mode].per_page,
options: {
15: 15,
20: 20,
25: 25,
30: 30,
35: 35,
},
class: 'medium',
# item_class: 'keepleft',
},
{
name: 'attributes',
display: 'Attributes',
tag: 'checkbox',
default: @overview.view[@view_mode].overview,
null: false,
options: {
# true: 'internal',
# false: 'public',
number: 'Number',
title: 'Title',
customer: 'Customer',
ticket_state: 'State',
ticket_priority: 'Priority',
group: 'Group',
owner: 'Owner',
created_at: 'Alter',
last_contact: 'Last Contact',
last_contact_agent: 'Last Contact Agent',
last_contact_customer: 'Last Contact Customer',
first_response: 'First Response',
close_time: 'Close Time',
},
class: 'medium',
},
{
name: 'order_by',
display: 'Order',
tag: 'select',
default: @overview.order.by,
null: false,
options: {
number: 'Number',
title: 'Title',
customer: 'Customer',
ticket_state: 'State',
ticket_priority: 'Priority',
group: 'Group',
owner: 'Owner',
created_at: 'Alter',
last_contact: 'Last Contact',
last_contact_agent: 'Last Contact Agent',
last_contact_customer: 'Last Contact Customer',
first_response: 'First Response',
close_time: 'Close Time',
},
class: 'medium',
},
{
name: 'order_by_direction',
display: 'Direction',
tag: 'select',
default: @overview.order.direction,
null: false,
options: {
ASC: 'up',
DESC: 'down',
},
class: 'medium',
},
# {
# name: 'condition',
# display: 'Conditions',
# tag: 'select',
# multiple: false,
# null: false,
# relation: 'TicketArticleType',
# default: '9',
# class: 'medium',
# item_class: 'keepleft',
# },
]
form = @formGen( model: { configure_attributes: @configure_attributes_article } )
@el.find('.setting').append(form)
@modalShow()
submit: (e) =>
e.preventDefault()
params = @formParam(e.target)
# check if refetch is needed
@reload_needed = 0
if @overview.view[@view_mode]['per_page'] isnt params['per_page']
@overview.view[@view_mode]['per_page'] = params['per_page']
@reload_needed = 1
if @overview.order['by'] isnt params['order_by']
@overview.order['by'] = params['order_by']
@reload_needed = 1
if @overview.order['direction'] isnt params['order_by_direction']
@overview.order['direction'] = params['order_by_direction']
@reload_needed = 1
@overview.view[@view_mode]['overview'] = params['attributes']
@overview.save(
success: =>
if @reload_needed
@overview.trigger('local:refetch')
else
@overview.trigger('local:rerender')
)
@modalHide()
Config.Routes['ticket/view/:view'] = Index

View file

@ -0,0 +1,381 @@
$ = jQuery.sub()
class Index extends App.Controller
events:
'click .submit': 'update',
'click [data-type=reply]': 'reply',
'click [data-type=reply-all]': 'replyall',
'click [data-type=public]': 'public_internal',
'click [data-type=internal]': 'public_internal',
'click [data-type=history]': 'history_view',
'change [name="ticket_article_type_id"]': 'form_update',
constructor: (params) ->
super
@log 'zoom', params
# check authentication
return if !@authenticate()
@navupdate '#'
@edit_form = undefined
# @render()
@ticket_id = params.ticket_id
@fetch(@ticket_id)
fetch: (ticket_id) ->
# get data
@ajax = new App.Ajax
@ajax.ajax(
type: 'GET',
url: '/ticket_full/' + ticket_id,
data: {
view: @view
}
processData: true,
success: (data, status, xhr) =>
# reset old indexes
@ticket = undefined
@articles = undefined
# get edit form attributes
@edit_form = data.edit_form
# load user collection
@loadCollection( type: 'User', data: data.users )
# load ticket collection
@loadCollection( type: 'Ticket', data: [data.ticket] )
# load article collections
@loadCollection( type: 'TicketArticle', data: data.articles || [] )
# render page
@render()
)
render: =>
if !App.Ticket.exists(@ticket_id)
return
# get data
if !@ticket
@ticket = App.Ticket.find(@ticket_id)
if !@articles
@articles = []
for article_id in @ticket.article_ids
@articles.push App.TicketArticle.find(article_id)
# check attachments
for article in @articles
if article.attachments
for attachment in article.attachments
attachment.size = @humanFileSize(attachment.size)
# define actions
for article in @articles
actions = []
if article.internal is true
actions = [
{
name: 'set to public',
type: 'public',
}
]
else
actions = [
{
name: 'set to internal',
type: 'internal',
}
]
if article.article_type.name is 'note'
# actions.push []
else
if article.article_sender.name is 'Customer'
actions.push {
name: 'reply',
type: 'reply',
}
actions.push {
name: 'reply all',
type: 'reply-all',
}
article.actions = actions
# set title
@title 'Ticket Zoom ' + @ticket.number
@configure_attributes_ticket = [
{ name: 'ticket_state_id', display: 'State', tag: 'select', multiple: false, null: true, relation: 'TicketState', default: 'new', class: 'span2', item_class: 'keepleft' },
{ name: 'ticket_priority_id', display: 'Priority', tag: 'select', multiple: false, null: true, relation: 'TicketPriority', default: '2 normal', class: 'span2', item_class: 'keepleft' },
{ name: 'group_id', display: 'Group', tag: 'select', multiple: false, null: true, relation: 'Group', class: 'span2', item_class: 'keepleft' },
{ name: 'owner_id', display: 'Owner', tag: 'select', multiple: false, null: true, relation: 'User', filter: @edit_form, nulloption: true, class: 'span2', item_class: 'keepleft' },
]
form_ticket = @formGen( model: { configure_attributes: @configure_attributes_ticket, className: 'create' }, params: @ticket )
@configure_attributes_article = [
# { name: 'from', display: 'From', tag: 'input', type: 'text', limit: 100, null: false, class: 'span8', },
{ name: 'to', display: 'To', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide' },
{ name: 'cc', display: 'Cc', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide' },
{ name: 'subject', display: 'Subject', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide' },
{ name: 'in_reply_to', display: 'In Reply to', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide' },
{ name: 'body', display: 'Text', tag: 'textarea', rows: 5, limit: 100, null: true, class: 'span7', },
{ name: 'ticket_article_type_id', display: 'Type', tag: 'select', multiple: false, null: true, relation: 'TicketArticleType', default: '9', class: 'medium', item_class: 'keepleft' },
{ name: 'internal', display: 'Visability', tag: 'radio', default: false, null: true, options: { true: 'internal', false: 'public' }, class: 'medium', item_class: 'keepleft' },
# { name: 'ticket_article_sender_id', display: 'Sender', tag: 'select', multiple: false, null: true, relation: 'TicketArticleSender', default: '', class: 'medium' },
]
form_article = @formGen( model: { configure_attributes: @configure_attributes_article } )
@html App.view('agent_ticket_zoom')(
ticket: @ticket,
articles: @articles,
form_ticket: form_ticket,
form_article: form_article,
)
@userPopups()
# start customer info controller
new App.UserInfo(
el: @el.find('#customer_info'),
user_id: @ticket.customer_id,
ticket: @ticket,
)
@delay(@u, 200)
u: =>
uploader = new qq.FileUploader(
element: document.getElementById('file-uploader'),
action: 'ticket_attachment_new',
params: {
form: 'TicketZoom',
form_id: @ticket.id,
},
debug: false
)
history_view: (e) ->
e.preventDefault()
new History( ticket_id: @ticket_id )
public_internal: (e) ->
e.preventDefault()
article_id = $(e.target).parents('[data-id]').data('id')
# storage update
article = App.TicketArticle.find(article_id)
internal = true
if article.internal == true
internal = false
article.updateAttributes(
internal: internal
)
# runtime update
for article in @articles
if article_id is article.id
article['internal'] = internal
@render()
form_update: (e) ->
ticket_article_type_id = $(e.target).find('option:selected').val()
@log 'eeee', e, ticket_article_type_id
article_type = App.TicketArticleType.find( ticket_article_type_id )
@form_update_execute(article_type)
form_update_execute: (article_type) =>
if article_type.name is 'twitter status'
# hide to
@el.find('[name="to"]').parents('.control-group').addClass('hide')
@el.find('[name="cc"]').parents('.control-group').addClass('hide')
@el.find('[name="subject"]').parents('.control-group').addClass('hide')
else if article_type.name is 'twitter direct-message'
# show
@el.find('[name="to"]').parents('.control-group').removeClass('hide')
@el.find('[name="cc"]').parents('.control-group').addClass('hide')
@el.find('[name="subject"]').parents('.control-group').addClass('hide')
else if article_type.name is 'note'
# hide to
@el.find('[name="to"]').parents('.control-group').addClass('hide')
@el.find('[name="cc"]').parents('.control-group').addClass('hide')
@el.find('[name="subject"]').parents('.control-group').addClass('hide')
else if article_type.name is 'email'
# show
@el.find('[name="to"]').parents('.control-group').removeClass('hide')
@el.find('[name="cc"]').parents('.control-group').removeClass('hide')
# @el.find('[name="subject"]').parents('.control-group').removeClass('hide')
reply: (e) =>
e.preventDefault()
article_id = $(e.target).parents('[data-id]').data('id')
article = App.TicketArticle.find( article_id )
article_type = App.TicketArticleType.find( article.ticket_article_type_id )
customer = App.User.find( article.created_by_id )
@log 'reply', e, article_type
# update form
@form_update_execute(article_type)
# preselect article type
@el.find('[name="ticket_article_type_id"]').find('option:selected').removeAttr('selected')
@el.find('[name="ticket_article_type_id"]').find('[value="' + article_type.id + '"]').attr('selected',true)
# empty form
@el.find('[name="to"]').val('')
@el.find('[name="cc"]').val('')
@el.find('[name="subject"]').val('')
@el.find('[name="in_reply_to"]').val('')
if article.message_id
@el.find('[name="in_reply_to"]').val(article.message_id)
if article_type.name is 'twitter status'
# set to in body
to = customer.accounts['twitter'].username || customer.accounts['twitter'].uid
@log 'c', customer
@el.find('[name="body"]').val('@' + to)
else if article_type.name is 'twitter direct-message'
# show to
to = customer.accounts['twitter'].username || customer.accounts['twitter'].uid
@el.find('[name="to"]').val(to)
else if article_type.name is 'email'
@el.find('[name="to"]').val(article.from)
# @log 'reply ', article, @el.find('[name="to"]')
update: (e) =>
e.preventDefault()
params = @formParam(e.target)
@log 'update', params, @ticket
# update ticket
ticket_update = {}
for item in @configure_attributes_ticket
ticket_update[item.name] = params[item.name]
# check owner assignment
if !ticket_update['owner_id']
ticket_update['owner_id'] = 1
@ticket.load( ticket_update )
@log 'update ticket', ticket_update, @ticket
# disable form
@formDisable(e)
@ticket.save(
success: (r) =>
# create article
if params['body']
article = new App.TicketArticle
params.from = window.Session['firstname'] + ' ' + window.Session['lastname']
params.ticket_id = @ticket.id
# find sender_id
sender = App.TicketArticleSender.findByAttribute("name", "Agent")
params.ticket_article_sender_id = sender.id
@log 'updateAttributes', params, sender, sender.id
article.load(params)
article.save(
success: (r) =>
@fetch(@ticket.id)
)
else
@fetch(@ticket.id)
)
# errors = article.validate()
# @log 'error new', errors
# @validateForm( form: e.target, errors: errors )
return false
class History extends App.ControllerModal
constructor: ->
super
@fetch(@ticket_id)
fetch: (@ticket_id) ->
# get data
@ajax = new App.Ajax
@ajax.ajax(
type: 'GET',
url: '/ticket_history/' + ticket_id,
data: {
# view: @view
}
# processData: true,
success: (data, status, xhr) =>
# remember ticket
@ticket = data.ticket
# load user collection
@loadCollection( type: 'User', data: data.users )
# load ticket collection
@loadCollection( type: 'Ticket', data: [data.ticket] )
# load history_type collections
@loadCollection( type: 'HistoryType', data: data.history_types )
# load history_object collections
@loadCollection( type: 'HistoryObject', data: data.history_objects )
# load history_attributes collections
@loadCollection( type: 'HistoryAttribute', data: data.history_attributes )
# load history collections
App.History.deleteAll()
@loadCollection( type: 'History', data: data.history )
# render page
@render()
)
render: ->
# create table/overview
table = @table(
overview_extended: [
{ name: 'type', },
{ name: 'attribute', },
{ name: 'value_from', },
{ name: 'value_to', },
{ name: 'created_by', class: 'user-data', data: { id: 1 } },
{ name: 'created_at', callback: @humanTime },
],
model: App.History,
objects: App.History.all(),
)
@html App.view('agent_ticket_history')(
# head: 'New User',
# form: @formGen( model: App.User, required: 'quick' ),
)
@el.find('.table_history').append(table)
@modalShow()
@userPopups()
Config.Routes['ticket/zoom/:ticket_id'] = Index
Config.Routes['ticket/zoom/:ticket_id/:article_id'] = Index

View file

@ -0,0 +1,26 @@
$ = jQuery.sub()
class Index extends App.ControllerLevel2
toggleable: true
menu: [
{ name: 'Web', 'target': 'web', controller: App.ChannelWeb },
{ name: 'Mail', 'target': 'email', controller: App.ChannelEmail },
{ name: 'Chat', 'target': 'chat', controller: App.ChannelChat },
{ name: 'Twitter', 'target': 'twitter', controller: App.ChannelTwitter },
{ name: 'Facebook', 'target': 'facebook', controller: App.ChannelFacebook },
]
page: {
title: 'Channels',
sub_title: 'Management'
nav: '#channels',
}
constructor: ->
super
# render page
@render()
Config.Routes['channels'] = Index

View file

@ -0,0 +1,60 @@
$ = jQuery.sub()
class Index extends App.Controller
constructor: ->
super
# check authentication
return if !@authenticate()
# set title
@title 'Dashboard'
@navupdate '#/'
@plugins = {
main: {
my_assigned: {
controller: App.DashboardTicket,
params: {
view: 'my_assigned',
},
},
all_unassigned: {
controller: App.DashboardTicket,
params: {
view: 'all_unassigned',
},
},
},
side: {
activity_stream: {
controller: App.DashboardActivityStream,
},
recent_viewed: {
controller: App.DashboardRecentViewed,
}
}
}
# render page
@render()
render: ->
@html App.view('dashboard')(
head: 'Dashboard'
)
for area, plugins of @plugins
for name, plugin of plugins
target = area + '_' + name
@el.find('.' + area + '-overviews').append('<div class="" id="' + target + '"></div>')
if plugin.controller
params = plugin.params || {}
params.el = @el.find( '#' + target )
new plugin.controller( params )
Config.Routes[''] = Index
Config.Routes['/'] = Index

View file

@ -0,0 +1,74 @@
$ = jQuery.sub()
class Index extends App.Controller
className: 'container getstarted'
events:
'submit form': 'submit',
'click .submit': 'submit',
constructor: ->
super
# check authentication
return if !@authenticate()
# set title
@title 'Get Started'
@render()
@navupdate '#get_started'
render: ->
@html App.view('getting_started')(
form: @formGen( model: App.User, required: 'invite_agent' ),
)
cancel: ->
@log 'cancel....'
@navigate 'login'
submit: (e) ->
@log 'submit'
e.preventDefault()
@params = @formParam(e.target)
# if no login is given, use emails as fallback
if !@params.login && @params.email
@params.login = @params.email
# find agent role
role = App.Role.findByAttribute("name", "Agent")
@params.role_ids = role.id
# set invite flag
@params.invite = true
@log 'updateAttributes', @params
user = new App.User
user.load(@params)
errors = user.validate()
if errors
@log 'error new', errors
@validateForm( form: e.target, errors: errors )
return false
# save user
user.save(
success: (r) =>
# send email
# clear form
@render()
# error: =>
# @modalHide()
)
Config.Routes['getting_started'] = Index
#class App.GetStarted extends App.Router
# routes:
# 'getting_started': Index
#Config.Controller.push App.GetStarted;

View file

@ -0,0 +1,37 @@
$ = jQuery.sub()
class Index extends App.Controller
constructor: ->
super
# check authentication
return if !@authenticate()
new App.ControllerGenericIndex(
el: @el,
id: @id,
genericObject: App.Group,
pageData: {
title: 'Groups',
home: 'groups',
object: 'Group',
objects: 'Groups',
navupdate: '#groups',
notes: [
'Groups are ...'
],
buttons: [
{ name: 'New Group', 'data-type': 'new', class: 'primary' },
],
},
)
Config.Routes['groups'] = Index
#class App.Groups extends App.Router
# routes:
# 'groups/new': New
# 'groups/:id/edit': Edit
# 'groups': Index
#Config.Controller.push App.Groups

View file

@ -0,0 +1,97 @@
$ = jQuery.sub()
Note = App.Note
$.fn.item = ->
elementID = $(@).data('id')
elementID or= $(@).parents('[data-id]').data('id')
Note.find(elementID)
class Index extends App.Controller
events:
'submit #login': 'login',
'click #register': 'register'
constructor: ->
super
@title 'Sign in'
@render()
@navupdate '#login'
render: (data = {}) ->
@html App.view('login')(item: data)
if $(@el).find('[name="username"]').val()
$(@el).find('[name="username"]').focus()
login: (e) ->
e.preventDefault()
e.stopPropagation();
@log 'submit', $(e.target)
@username = $(e.target).find('[name="username"]').val()
@password = $(e.target).find('[name="password"]').val()
# @log @username, @password
# session create with login/password
auth = new App.Auth
auth.login(
data: {
username: @username,
password: @password,
},
success: @success
error: @error,
)
success: (data, status, xhr) =>
@log 'login:success', data
# set avatar
if !data.session.image
data.session.image = 'http://placehold.it/48x48'
# update config
for key, value of data.config
window.Config[key] = value
# store user data
for key, value of data.session
window.Session[key] = value
# refresh default collections
for key, value of data.default_collections
App[key].refresh( value, options: { clear: true } )
Spine.trigger 'navrebuild', data.session
# add notify
Spine.trigger 'notify:removeall'
Spine.trigger 'notify', {
type: 'success',
msg: 'Login successfully! Have a nice day!',
}
# redirect to #
@navigate '#/'
error: (xhr, statusText, error) =>
console.log 'login:error'
# add notify
Spine.trigger 'notify:removeall'
Spine.trigger 'notify', {
type: 'warning',
msg: 'Wrong Username and Password combination.',
}
# rerender login page
@render(
msg: 'Wrong Username and Password combination.',
username: @username
)
Config.Routes['login'] = Index
#class App.Login extends App.Router
# routes:
# 'login': Index
#Config.Controller.push App.Login

View file

@ -0,0 +1,30 @@
$ = jQuery.sub()
class Index extends Spine.Controller
constructor: ->
super
@signout()
signout: ->
# remove remote session
auth = new App.Auth
auth.logout()
# remoce local session
@log 'Session', window.Session
window.Session = {}
@log 'Session', window.Session
Spine.trigger 'navrebuild'
# redirect to login
@navigate 'login'
Config.Routes['logout'] = Index
#class App.Logout extends App.Router
# routes:
# 'logout': Index
#Config.Controller.push App.Logout

View file

@ -0,0 +1,169 @@
$ = jQuery.sub()
Note = App.Note
$.fn.item = ->
elementID = $(@).data('id')
elementID or= $(@).parents('[data-id]').data('id')
Note.find(elementID)
class App.Navigation extends Spine.Controller
events:
'focusin [data-type=edit]': 'edit_in'
constructor: ->
super
@log 'nav...'
@render()
Spine.bind 'navupdate', (data) =>
@update(arguments[0])
Spine.bind 'navrebuild', (user) =>
@log 'navbarrebuild', user
@render(user)
Spine.bind 'navupdate_remote', (user) =>
@log 'navupdate_remote'
@sync
# rerender if new overview data is there
@delay( @sync, 1800 )
render: (user) ->
# @log 'nav render', Config.NavBar
# @log '111', _.keys(Config.NavBar)
navbar = _.values(Config.NavBar)
level1 = []
dropdown = {}
for item in navbar
if !item.parent
match = 0
if !window.Session['roles']
match = _.include(item.role, 'Anybody')
if window.Session['roles']
window.Session['roles'].forEach( (role) =>
if !match
match = _.include(item.role, role.name)
)
if match
level1.push item
for item in navbar
if item.parent && !dropdown[ item.parent ]
dropdown[ item.parent ] = []
# find all childs and order
for itemSub in navbar
if itemSub.parent is item.parent
match = 0
if !window.Session['roles']
match = _.include(itemSub.role, 'Anybody')
if window.Session['roles']
window.Session['roles'].forEach( (role) =>
if !match
match = _.include(itemSub.role, role.name)
)
if match
dropdown[ item.parent ].push itemSub
# find parent
for itemLevel1 in level1
if itemLevel1.target is item.parent
sub = @getOrder(dropdown[ item.parent ])
itemLevel1.child = sub
nav = @getOrder(level1)
@html App.view('navigation')(
navbar: nav,
user: user,
)
getOrder: (data) ->
newlist = {}
for item in data
# check if same prio already exists
@addPrioCount newlist, item
newlist[ item['prio'] ] = item;
# get keys for sort order
keys = _.keys(newlist)
inorder = keys.sort(@sortit)
# create new array with prio sort order
inordervalue = []
for num in inorder
inordervalue.push newlist[ num ]
return inordervalue
sortit: (a,b) ->
return(a-b)
addPrioCount: (newlist, item) ->
if newlist[ item['prio'] ]
item['prio']++
if newlist[ item['prio'] ]
@addPrioCount newlist, item
update: (url) =>
@el.find('li').removeClass('active')
# if url isnt '#'
@el.find("[href=\"#{url}\"]").parents('li').addClass('active')
# @el.find("[href*=\"#{url}\"]").parents('li').addClass('active')
sync: =>
@ticket_overview()
# auto save
every = (ms, cb) -> setInterval cb, ms
# clear auto save
clearInterval(@intervalID) if @intervalID
# request new data
@intervalID = every 40000, () =>
@ticket_overview()
# get data
ticket_overview: =>
# do no load and rerender if sub-menu is open
open = @el.find('.open').val()
if open isnt undefined
return
# do no load and rerender if user is not logged in
if !window.Session['id']
return
@ajax = new App.Ajax
@ajax.ajax(
type: 'GET',
url: '/ticket_overviews',
data: {},
processData: true,
success: (data, status, xhr) =>
# remove old views
for key of Config.NavBar
if Config.NavBar[key].parent is '#ticket/view'
delete Config.NavBar[key]
# add new views
for item in data
Config.NavBar['TicketOverview' + item.url] = {
prio: item.prio,
parent: '#ticket/view',
name: item.name + ' (' + item.count + ')',
target: '#ticket/view/' + item.url,
role: ['Agent'],
}
# rebuild navbar
Spine.trigger 'navrebuild', window.Session
)

View file

@ -0,0 +1,71 @@
$ = jQuery.sub()
Note = App.Note
$.fn.item = ->
elementID = $(@).data('id')
elementID or= $(@).parents('[data-id]').data('id')
Note.find(elementID)
class Index extends App.Controller
events:
'click [data-type=network-new]': 'network_new'
'click [data-type=network-edit]': 'network_edit'
'click [data-type=network-destroy]': 'network_destory'
'click [data-type=network-category-new]': 'network_category_new'
'click [data-type=network-category-edit]': 'network_category_edit'
'click [data-type=network-category-destroy]': 'network_category_destroy'
constructor: ->
super
# set title
@title 'Network'
@render()
@navupdate '#network'
render: ->
networks = App.Network.all()
network_categories = App.NetworkCategory.all()
for network in networks
@log 'f', network for network in networks
for network_category in network_categories
@log 'fc', network_category
@html App.view('network')(
networks: App.Network.all(),
)
network_new: (e) ->
e.preventDefault()
new App.ControllerGenericNewWindow(
pageData: {
object: 'Network',
},
genericObject: App.Network,
success: =>
@render()
)
network_edit: (e) ->
e.preventDefault()
@id = $(e.target).parents('[data-id]').data('id')
new App.ControllerGenericEditWindow(
id: @id,
pageData: {
object: 'Network',
},
genericObject: App.Network,
success: =>
@render()
)
network_destory: (e) ->
e.preventDefault()
id = $(e.target).parents('[data-id]').data('id')
item = App.Network.find(id)
item.destroy() if confirm('Sure?')
@render()
Config.Routes['network'] = Index

View file

@ -0,0 +1,44 @@
$ = jQuery.sub()
#Post = App.Post
class App.Notify extends Spine.Controller
events:
'click .alert': 'destroy'
className: 'container'
constructor: ->
super
Spine.bind 'notify', (data) =>
# @log 'bind notify', data
@[data.type] data.msg
Spine.bind 'notify:removeall', =>
@log 'notify:removeall', @
@destroyAll()
info: (data) ->
@render( text: arguments[0], type: 'alert-info' )
warning: (data) ->
@render( text: arguments[0], type: 'alert-warning' )
error: (data) ->
@render( text: arguments[0], type: 'alert-error' )
success: (data) ->
@render( text: arguments[0], type: 'alert-success' )
render: (data) ->
notify = App.view('notify')(data: data)
@append( notify )
# notify.html('')
destroy: (e) ->
e.preventDefault()
$(e.target).parents('.alert').remove();
destroyAll: ->
$(@el).find('.alert').remove();

View file

@ -0,0 +1,65 @@
$ = jQuery.sub()
class Index extends App.Controller
constructor: ->
super
# check authentication
return if !@authenticate()
new App.ControllerGenericIndex(
el: @el,
id: @id,
genericObject: App.Organization,
pageData: {
title: 'Organizations',
home: 'organizations',
object: 'Organization',
objects: 'Organizations',
navupdate: '#organizations',
notes: [
'Organizations are for any person in the system. Agents (Owners, Resposbiles, ...) and Customers.'
],
buttons: [
{ name: 'New Organization', 'data-type': 'new', class: 'primary' },
],
},
)
#Config.Routes['organizations/new'] = New
#Config.Routes['organizations/:id/edit'] = Edit
Config.Routes['organizations'] = Index
Config.NavBar['Admin'] = { prio: 10000, parent: '', name: 'Manage', target: '#admin', role: ['Admin'] }
Config.NavBar['AdminUser'] = { prio: 1000, parent: '#admin', name: 'Users', target: '#users', role: ['Admin'] }
Config.NavBar['AdminGroup'] = { prio: 1500, parent: '#admin', name: 'Groups', target: '#groups', role: ['Admin'] }
Config.NavBar['AdminOrganization'] = { prio: 2000, parent: '#admin', name: 'Organizations', target: '#organizations', role: ['Admin'] }
Config.NavBar['AdminChannels'] = { prio: 2500, parent: '#admin', name: 'Channels', target: '#channels', role: ['Admin'] }
Config.NavBar['AdminTrigger'] = { prio: 3000, parent: '#admin', name: 'Trigger', target: '#trigger', role: ['Admin'] }
Config.NavBar['AdminScheduler'] = { prio: 3500, parent: '#admin', name: 'Scheduler', target: '#scheduler', role: ['Admin'] }
Config.NavBar['Note'] = { prio: 1500, parent: '', name: 'Notes', target: '#notes', role: ['Notes'] }
#Config.NavBar['Post'] = { prio: 1600, parent: '', name: 'Posts', target: '#posts', role: ['Agent'] }
Config.NavBar['Setting'] = { prio: 20000, parent: '', name: 'Settings', target: '#settings', role: ['Admin'] }
Config.NavBar['SettingSystem'] = { prio: 1400, parent: '#settings', name: 'System', target: '#settings/system', role: ['Admin'] }
Config.NavBar['SettingSecurity'] = { prio: 1500, parent: '#settings', name: 'Security', target: '#settings/security', role: ['Admin'] }
Config.NavBar['SettingTicket'] = { prio: 1600, parent: '#settings', name: 'Ticket', target: '#settings/ticket', role: ['Admin'] }
Config.NavBar['SettingObject'] = { prio: 1700, parent: '#settings', name: 'Objects', target: '#settings/objects', role: ['Admin'] }
Config.NavBar['Packages'] = { prio: 1800, parent: '#settings', name: 'Packages', target: '#packages', role: ['Admin'] }
Config.NavBar['TicketOverview'] = { prio: 1000, parent: '', name: 'Overviews', target: '#ticket/view', role: ['Agent'] }
#Config.NavBar[''] = { prio: 1000, parent: '#ticket/view', name: 'My assigned Tickets (51)', target: '#ticket/view/my_assigned', role: ['Agent'] }
#Config.NavBar[''] = { prio: 1000, parent: '#ticket/view', name: 'Unassigned Tickets (133)', target: '#ticket/view/all_unassigned', role: ['Agent'] }
#Config.NavBar[''] = { prio: 1000, parent: '#ticket/view', name: 'Escalated Tickets (0)', target: '#ticket/view/all_escalated', role: ['Agent'] }
#Config.NavBar[''] = { prio: 1000, parent: '#ticket/view', name: 'My Pending reached Tickets (2)', target: '#ticket/view/my_pending_reached', role: ['Agent'] }
#Config.NavBar['Network'] = { prio: 1500, parent: '', name: 'Networking', target: '#network', role: ['Anybody', 'Customer', 'Agent'] }
#Config.NavBar[''] = { prio: 1600, parent: '', name: 'anybody+agent', target: '#aa', role: ['Anybody', 'Agent'] }
#Config.NavBar[''] = { prio: 1600, parent: '', name: 'Anybody', target: '#anybody', role: ['Anybody'] }
Config.NavBar['CustomerTickets'] = { prio: 1600, parent: '', name: 'Tickets', target: '#customer_tickets', role: ['Customer'] }

View file

@ -0,0 +1,32 @@
$ = jQuery.sub()
Note = App.Note
$.fn.item = ->
elementID = $(@).data('id')
elementID or= $(@).parents('[data-id]').data('id')
Note.find(elementID)
class Index extends App.Controller
events:
'focusin [data-type=edit]': 'edit_in'
constructor: ->
super
# set title
@title 'Profile'
@render()
@navupdate '#profile'
render: ->
@html App.view('profile')()
Config.Routes['profile'] = Index
#class App.Profile extends App.Router
# routes:
# 'profile': Index
#Config.Controller.push App.Profile

View file

@ -0,0 +1,23 @@
$ = jQuery.sub()
class Index extends App.Controller
constructor: ->
super
# check authentication
return if !@authenticate()
# set title
@title 'Scheduler'
@navupdate '#scheduler'
# render page
@render()
render: ->
@html App.view('scheduler')(
head: 'some header'
)
Config.Routes['scheduler'] = Index

View file

@ -0,0 +1,55 @@
$ = jQuery.sub()
class Index extends App.ControllerLevel2
toggleable: false
toggleable: true
constructor: ->
super
# system
if @type is 'system'
@menu = [
{ name: 'Base', 'target': 'base', controller: App.SettingsArea, params: { area: 'System::Base' } },
# { name: 'Language', 'target': 'language', controller: App.SettingsSystem, params: { area: 'System::Language' } },
# { name: 'Log', 'target': 'log', controller: App.SettingsSystem, params: { area: 'System::Log' } },
{ name: 'Storage', 'target': 'storage', controller: App.SettingsArea, params: { area: 'System::Storage' } },
]
@page = {
title: 'System',
sub_title: 'Settings'
nav: '#settings/system',
}
# security
if @type is 'security'
@menu = [
{ name: 'Authentication', 'target': 'auth', controller: App.SettingsArea, params: { area: 'Security::Authentication' } },
{ name: 'Password', 'target': 'password', controller: App.SettingsArea, params: { area: 'Security::Password' } },
# { name: 'Session', 'target': 'session', controller: '' },
]
@page = {
title: 'Security',
sub_title: 'Settings'
nav: '#settings/security',
}
# ticket
if @type is 'ticket'
@menu = [
{ name: 'Base', 'target': 'base', controller: App.SettingsArea, params: { area: 'Ticket::Base' } },
{ name: 'Number', 'target': 'number', controller: App.SettingsArea, params: { area: 'Ticket::Number' } },
{ name: 'Sender Format', 'target': 'sender-format', controller: App.SettingsArea, params: { area: 'Ticket::SenderFormat' } },
]
@page = {
title: 'Ticket',
sub_title: 'Settings'
nav: '#settings/ticket',
}
# render page
@render()
Config.Routes['settings/:type/:target'] = Index
Config.Routes['settings/:type'] = Index

View file

@ -0,0 +1,121 @@
$ = jQuery.sub()
User = App.User
class Index extends App.Controller
className: 'container signup'
events:
'submit form': 'submit',
'click .submit': 'submit',
'click .cancel': 'cancel',
constructor: ->
super
# set title
@title 'Sign up'
@render()
@navupdate '#signup'
render: ->
# set password as required
for item in User.configure_attributes
if item.name is 'password'
item.null = false
@html App.view('signup')(
form: @formGen( model: User, required: 'signup' ),
)
cancel: ->
@log 'cancel....'
@navigate 'login'
submit: (e) ->
@log 'submit'
e.preventDefault()
@params = @formParam(e.target)
###
for num in [1..199]
user = new User
params.login = 'login_c' + num
user.updateAttributes(params)
return false
###
# if no login is given, use emails as fallback
if !@params.login && @params.email
@params.login = @params.email
# role = App.Role.findByAttribute("name", "Customer")
# @params.role_ids = role.id
# @params.role_ids = 3
@params.role_ids = []
@log 'updateAttributes', @params
user = new User
user.load(@params)
errors = user.validate()
if errors
@log 'error new', errors
@validateForm( form: e.target, errors: errors )
return false
# save user
user.save(
success: (r) =>
auth = new App.Auth
auth.login(
data: {
username: @params.login,
password: @params.password,
},
success: @success
error: @error,
)
# error: =>
# @modalHide()
)
success: (data, status, xhr) =>
@log 'login:success', data
# login check
auth = new App.Auth
auth.loginCheck()
# add notify
Spine.trigger 'notify:removeall'
@notify
type: 'success',
msg: 'Thanks for joining. Email sent to "' + @params.email + '". Please verify your email address.'
# redirect to #
@navigate '#'
error: (xhr, statusText, error) =>
console.log 'login:error'
# add notify
Spine.trigger 'notify:removeall'
Spine.trigger 'notify', {
type: 'warning',
msg: 'Wrong Username and Password combination.',
}
# rerender login page
@render(
msg: 'Wrong Username and Password combination.',
username: @username
)
Config.Routes['signup'] = Index
#class App.SignUp extends App.Router
# routes:
# 'signup': Index
#Config.Controller.push App.SignUp

View file

@ -0,0 +1,34 @@
$ = jQuery.sub()
class Index extends App.Controller
constructor: ->
super
# check authentication
return if !@authenticate()
# set title
@title 'Triggers'
@navupdate '#trigger'
# render page
@render()
render: ->
@html App.view('trigger')(
head: 'some header'
)
Config.Routes['trigger'] = Index
#class App.Triggers extends App.Router
# routes:
# 'triggers/web': New
# 'triggers/email': New
# 'triggers/twitter': New
# 'triggers/facebook': New
# 'triggers/new': New
# 'triggers/:id/edit': Edit
# 'triggers': Index
#
#Config.Controller.push App.Triggers

View file

@ -0,0 +1,68 @@
$ = jQuery.sub()
class App.UserInfo extends App.Controller
events:
'focusout [data-type=edit]': 'update',
constructor: ->
super
# fetch item on demand
fetch_needed = 1
if App.User.exists(@user_id)
@user = App.User.find(@user_id)
@log 'exists', @user
fetch_needed = 0
@render()
if fetch_needed
@reload(@user_id)
reload: (user_id) =>
App.User.bind 'refresh', =>
@log 'loading....', user_id
@user = App.User.find(user_id)
@render()
App.User.unbind 'refresh'
App.User.fetch( id: user_id )
render: ->
# define links to linked accounts
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 default image url
if !@user.image
@user.image = 'http://placehold.it/48x48'
# get display data
data = []
for item in App.User.configure_attributes
if item.name isnt 'firstname'
if item.name isnt 'lastname'
if item.info #&& ( @user[item.name] || item.name isnt 'note' )
data.push item
# insert data
@html App.view('user_info')(
user: @user,
data: data,
)
@userTicketPopups(
selector: '.user-tickets',
user_id: @user.id,
)
update: (e) =>
# update changes
note = $(e.target).parent().find('[data-type=edit]').val()
if @user.note isnt note
@user.updateAttributes( note: note )
@log 'update', e, note, @user

View file

@ -0,0 +1,39 @@
$ = jQuery.sub()
class Index extends App.Controller
constructor: ->
super
# check authentication
return if !@authenticate()
new App.ControllerGenericIndex(
el: @el,
id: @id,
genericObject: App.User,
ignoreObjectIDs: [1],
pageData: {
title: 'Users',
home: 'users',
object: 'User',
objects: 'Users',
navupdate: '#users',
notes: [
'Users are for any person in the system. Agents (Owners, Resposbiles, ...) and Customers.'
],
buttons: [
# { name: 'List', 'data-type': '', class: 'active' },
{ name: 'New User', 'data-type': 'new', class: 'primary' },
],
}
)
Config.Routes['users'] = Index
#class App.Users extends App.Router
# routes:
# 'users/new': New
# 'users/:id/edit': Edit
# 'users': Index
#
#Config.Controller.push App.Users

View file

@ -0,0 +1,195 @@
#s#= require json2
#= require jquery
#= require ./lib/spine/spine.coffee
#= require ./lib/spine/ajax.coffee
#= require ./lib/spine/route.coffee
#not_userd= require ./lib/spine/manager.coffee
#= require ./lib/bootstrap-dropdown.js
#= require ./lib/bootstrap-tooltip.js
#= require ./lib/bootstrap-popover.js
#= require ./lib/bootstrap-modal.js
#= require ./lib/bootstrap-tab.js
#= require ./lib/underscore.coffee
#= require ./lib/ba-linkify.js
#= require ./lib/ui/jquery.ui.core.js
#= require ./lib/ui/jquery.ui.widget.js
#= require ./lib/ui/jquery.ui.position.js
#= require ./lib/ui/jquery.ui.autocomplete.js
#not_used= require_tree ./lib/uis
#not_used= require ./lib/jquery.autocomplete.js
#= require ./lib/jquery.tagsinput.js
#= require ./lib/fileuploader.js
#not_used= require_tree ./lib
#= require_self
#= require_tree ./models
#= require_tree ./controllers
#= require_tree ./views
class App extends Spine.Controller
@view: (name) ->
JST["app/views/#{name}"]
###
class App.Config extends Spine.Module
constructor: ->
super
@config = {}
set: (key, value) =>
@config[key] = value
get: (key) =>
@config[key]
append: (key, value) =>
if !@config[key]
@config[key] = []
@config[key].push = value
Config2 = new App.Config
Config2.set( 'a', 123)
console.log '1112222', Config2.get( 'a')
###
class App.Ajax
defaults:
contentType: 'application/json'
dataType: 'json'
processData: false
headers: {'X-Requested-With': 'XMLHttpRequest'}
cache: false
ajax: (params, defaults) ->
$.ajax($.extend({}, @defaults, defaults, params))
class App.Auth extends App.Ajax
constructor: ->
console.log 'auth'
login: (params) ->
console.log 'login(...)', params
@ajax(
# params,
type: 'POST',
url: '/signin',
data: JSON.stringify(params.data),
success: params.success,
error: params.error,
)
loginCheck: ->
console.log 'loginCheck(...)'
@ajax(
async: false,
type: 'GET',
url: '/signshow',
success: (data, status, xhr) =>
console.log 'logincheck:success', data
# set avatar
if !data.session.image
data.session.image = 'http://placehold.it/48x48'
# update config
for key, value of data.config
window.Config[key] = value
# store user data
for key, value of data.session
window.Session[key] = value
# refresh/load default collections
for key, value of data.default_collections
App[key].refresh( value, options: { clear: true } )
# rebuild navbar with new navbar items
Spine.trigger 'navrebuild', data.session
# rebuild navbar with updated ticket count of overviews
Spine.trigger 'navupdate_remote'
error: (xhr, statusText, error) =>
console.log 'loginCheck:error'#, error, statusText, xhr.statusCode
# empty session
window.Session = {}
)
logout: ->
console.log 'logout(...)'
@ajax(
type: 'DELETE',
url: '/signout',
)
class App.Run extends Spine.Controller
constructor: ->
super
@log 'RUN app'#, @
@el = $('#app')
# start navigation controller
new App.Navigation( el: @el.find('#navigation') );
# check if session already exists/try to get session data from server
auth = new App.Auth
auth.loginCheck()
# start notify controller
new App.Notify( el: @el.find('#notify') );
# start content
new App.Content( el: @el.find('#content') );
#class App.Content extends Spine.Stack
class App.Content extends Spine.Controller
className: 'container'
constructor: ->
# @controllers = {}
# @routes = {}
# @default = '/'
# for route, controller of Config.Routes
## @log 'route,controller', route, controller
# @controllers[route] = controller
# @routes[route] = route
super
@log 'RUN content'#, @
for route, callback of Config.Routes
# @log 'route,controller', route#, controller
do (route, callback) =>
@route(route, (params) ->
# @log 'routing...', route
Config['ActiveController'] = route
# unbind in controller area
@el.unbind()
@el.undelegate()
params.el = @el
params.auth = @auth
new callback( params )
# scroll to top
# window.scrollTo(0,0)
)
# for name, object of Config.Controller
## @log 'new', object, @el
# new object( el: @el, auth: @auth )
Spine.Route.setup()
window.App = App

View file

View file

@ -0,0 +1,179 @@
/*!
* linkify - v0.3 - 6/27/2009
* http://benalman.com/code/test/js-linkify/
*
* Copyright (c) 2009 "Cowboy" Ben Alman
* Licensed under the MIT license
* http://benalman.com/about/license/
*
* Some regexps adapted from http://userscripts.org/scripts/review/7122
*/
// Turn text into linkified html.
//
// var html = linkify( text, options );
//
// options:
//
// callback (Function) - default: undefined - if defined, this will be called
// for each link- or non-link-chunk with two arguments, text and href. If the
// chunk is non-link, href will be omitted.
//
// punct_regexp (RegExp | Boolean) - a RegExp that can be used to trim trailing
// punctuation from links, instead of the default.
//
// This is a work in progress, please let me know if (and how) it fails!
window.linkify = (function(){
var
SCHEME = "[a-z\\d.-]+://",
IPV4 = "(?:(?:[0-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.){3}(?:[0-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])",
HOSTNAME = "(?:(?:[^\\s!@#$%^&*()_=+[\\]{}\\\\|;:'\",.<>/?]+)\\.)+",
TLD = "(?:ac|ad|aero|ae|af|ag|ai|al|am|an|ao|aq|arpa|ar|asia|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|biz|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|cat|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|coop|com|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|edu|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|info|int|in|io|iq|ir|is|it|je|jm|jobs|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mil|mk|ml|mm|mn|mobi|mo|mp|mq|mr|ms|mt|museum|mu|mv|mw|mx|my|mz|name|na|nc|net|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|org|pa|pe|pf|pg|ph|pk|pl|pm|pn|pro|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tel|tf|tg|th|tj|tk|tl|tm|tn|to|tp|travel|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|xn--0zwm56d|xn--11b5bs3a9aj6g|xn--80akhbyknj4f|xn--9t4b11yi5a|xn--deba0ad|xn--g6w251d|xn--hgbk6aj7f53bba|xn--hlcj6aya9esc7a|xn--jxalpdlp|xn--kgbechtv|xn--zckzah|ye|yt|yu|za|zm|zw)",
HOST_OR_IP = "(?:" + HOSTNAME + TLD + "|" + IPV4 + ")",
PATH = "(?:[;/][^#?<>\\s]*)?",
QUERY_FRAG = "(?:\\?[^#<>\\s]*)?(?:#[^<>\\s]*)?",
URI1 = "\\b" + SCHEME + "[^<>\\s]+",
URI2 = "\\b" + HOST_OR_IP + PATH + QUERY_FRAG + "(?!\\w)",
MAILTO = "mailto:",
EMAIL = "(?:" + MAILTO + ")?[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@" + HOST_OR_IP + QUERY_FRAG + "(?!\\w)",
URI_RE = new RegExp( "(?:" + URI1 + "|" + URI2 + "|" + EMAIL + ")", "ig" ),
SCHEME_RE = new RegExp( "^" + SCHEME, "i" ),
quotes = {
"'": "`",
'>': '<',
')': '(',
']': '[',
'}': '{',
'»': '«',
'': ''
},
default_options = {
callback: function( text, href ) {
// return href ? '<a href="' + href + '" title="' + href + '">' + text + '<\/a>' : text;
return href ? '<a href="' + href + '" title="' + href + '" target="_blank">' + text + '<\/a>' : text;
},
punct_regexp: /(?:[!?.,:;'"]|(?:&|&amp;)(?:lt|gt|quot|apos|raquo|laquo|rsaquo|lsaquo);)$/
};
return function( txt, options ) {
options = options || {};
// me
txt = txt
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
// me
// Temp variables.
var arr,
i,
link,
href,
// Output HTML.
html = '',
// Store text / link parts, in order, for re-combination.
parts = [],
// Used for keeping track of indices in the text.
idx_prev,
idx_last,
idx,
link_last,
// Used for trimming trailing punctuation and quotes from links.
matches_begin,
matches_end,
quote_begin,
quote_end;
// Initialize options.
for ( i in default_options ) {
if ( options[ i ] === undefined ) {
options[ i ] = default_options[ i ];
}
}
// Find links.
while ( arr = URI_RE.exec( txt ) ) {
link = arr[0];
idx_last = URI_RE.lastIndex;
idx = idx_last - link.length;
// Not a link if preceded by certain characters.
if ( /[\/:]/.test( txt.charAt( idx - 1 ) ) ) {
continue;
}
// Trim trailing punctuation.
do {
// If no changes are made, we don't want to loop forever!
link_last = link;
quote_end = link.substr( -1 )
quote_begin = quotes[ quote_end ];
// Ending quote character?
if ( quote_begin ) {
matches_begin = link.match( new RegExp( '\\' + quote_begin + '(?!$)', 'g' ) );
matches_end = link.match( new RegExp( '\\' + quote_end, 'g' ) );
// If quotes are unbalanced, remove trailing quote character.
if ( ( matches_begin ? matches_begin.length : 0 ) < ( matches_end ? matches_end.length : 0 ) ) {
link = link.substr( 0, link.length - 1 );
idx_last--;
}
}
// Ending non-quote punctuation character?
if ( options.punct_regexp ) {
link = link.replace( options.punct_regexp, function(a){
idx_last -= a.length;
return '';
});
}
} while ( link.length && link !== link_last );
href = link;
// Add appropriate protocol to naked links.
if ( !SCHEME_RE.test( href ) ) {
href = ( href.indexOf( '@' ) !== -1 ? ( !href.indexOf( MAILTO ) ? '' : MAILTO )
: !href.indexOf( 'irc.' ) ? 'irc://'
: !href.indexOf( 'ftp.' ) ? 'ftp://'
: 'http://' )
+ href;
}
// Push preceding non-link text onto the array.
if ( idx_prev != idx ) {
parts.push([ txt.slice( idx_prev, idx ) ]);
idx_prev = idx_last;
}
// Push massaged link onto the array
parts.push([ link, href ]);
};
// Push remaining non-link text onto the array.
parts.push([ txt.substr( idx_prev ) ]);
// Process the array items.
for ( i = 0; i < parts.length; i++ ) {
html += options.callback.apply( window, parts[i] );
}
// In case of catastrophic failure, return the original text;
return html || txt;
};
})();

View file

@ -0,0 +1,92 @@
/* ============================================================
* bootstrap-dropdown.js v2.0.0
* http://twitter.github.com/bootstrap/javascript.html#dropdowns
* ============================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================ */
!function( $ ){
"use strict"
/* DROPDOWN CLASS DEFINITION
* ========================= */
var toggle = '[data-toggle="dropdown"]'
, Dropdown = function ( element ) {
var $el = $(element).on('click.dropdown.data-api', this.toggle)
$('html').on('click.dropdown.data-api', function () {
$el.parent().removeClass('open')
})
}
Dropdown.prototype = {
constructor: Dropdown
, toggle: function ( e ) {
var $this = $(this)
, selector = $this.attr('data-target')
, $parent
, isActive
if (!selector) {
selector = $this.attr('href')
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
}
$parent = $(selector)
$parent.length || ($parent = $this.parent())
isActive = $parent.hasClass('open')
clearMenus()
!isActive && $parent.toggleClass('open')
return false
}
}
function clearMenus() {
$(toggle).parent().removeClass('open')
}
/* DROPDOWN PLUGIN DEFINITION
* ========================== */
$.fn.dropdown = function ( option ) {
return this.each(function () {
var $this = $(this)
, data = $this.data('dropdown')
if (!data) $this.data('dropdown', (data = new Dropdown(this)))
if (typeof option == 'string') data[option].call($this)
})
}
$.fn.dropdown.Constructor = Dropdown
/* APPLY TO STANDARD DROPDOWN ELEMENTS
* =================================== */
$(function () {
$('html').on('click.dropdown.data-api', clearMenus)
$('body').on('click.dropdown.data-api', toggle, Dropdown.prototype.toggle)
})
}( window.jQuery )

View file

@ -0,0 +1,209 @@
/* =========================================================
* bootstrap-modal.js v2.0.0
* http://twitter.github.com/bootstrap/javascript.html#modals
* =========================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================= */
!function( $ ){
"use strict"
/* MODAL CLASS DEFINITION
* ====================== */
var Modal = function ( content, options ) {
this.options = $.extend({}, $.fn.modal.defaults, options)
this.$element = $(content)
.delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this))
}
Modal.prototype = {
constructor: Modal
, toggle: function () {
return this[!this.isShown ? 'show' : 'hide']()
}
, show: function () {
var that = this
if (this.isShown) return
$('body').addClass('modal-open')
this.isShown = true
this.$element.trigger('show')
escape.call(this)
backdrop.call(this, function () {
var transition = $.support.transition && that.$element.hasClass('fade')
!that.$element.parent().length && that.$element.appendTo(document.body) //don't move modals dom position
that.$element
.show()
if (transition) {
that.$element[0].offsetWidth // force reflow
}
that.$element.addClass('in')
transition ?
that.$element.one($.support.transition.end, function () { that.$element.trigger('shown') }) :
that.$element.trigger('shown')
})
}
, hide: function ( e ) {
e && e.preventDefault()
if (!this.isShown) return
var that = this
this.isShown = false
$('body').removeClass('modal-open')
escape.call(this)
this.$element
.trigger('hide')
.removeClass('in')
$.support.transition && this.$element.hasClass('fade') ?
hideWithTransition.call(this) :
hideModal.call(this)
}
}
/* MODAL PRIVATE METHODS
* ===================== */
function hideWithTransition() {
var that = this
, timeout = setTimeout(function () {
that.$element.off($.support.transition.end)
hideModal.call(that)
}, 500)
this.$element.one($.support.transition.end, function () {
clearTimeout(timeout)
hideModal.call(that)
})
}
function hideModal( that ) {
this.$element
.hide()
.trigger('hidden')
backdrop.call(this)
}
function backdrop( callback ) {
var that = this
, animate = this.$element.hasClass('fade') ? 'fade' : ''
if (this.isShown && this.options.backdrop) {
var doAnimate = $.support.transition && animate
this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
.appendTo(document.body)
if (this.options.backdrop != 'static') {
this.$backdrop.click($.proxy(this.hide, this))
}
if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
this.$backdrop.addClass('in')
doAnimate ?
this.$backdrop.one($.support.transition.end, callback) :
callback()
} else if (!this.isShown && this.$backdrop) {
this.$backdrop.removeClass('in')
$.support.transition && this.$element.hasClass('fade')?
this.$backdrop.one($.support.transition.end, $.proxy(removeBackdrop, this)) :
removeBackdrop.call(this)
} else if (callback) {
callback()
}
}
function removeBackdrop() {
this.$backdrop.remove()
this.$backdrop = null
}
function escape() {
var that = this
if (this.isShown && this.options.keyboard) {
$(document).on('keyup.dismiss.modal', function ( e ) {
e.which == 27 && that.hide()
})
} else if (!this.isShown) {
$(document).off('keyup.dismiss.modal')
}
}
/* MODAL PLUGIN DEFINITION
* ======================= */
$.fn.modal = function ( option ) {
return this.each(function () {
var $this = $(this)
, data = $this.data('modal')
, options = typeof option == 'object' && option
if (!data) $this.data('modal', (data = new Modal(this, options)))
if (typeof option == 'string') data[option]()
else data.show()
})
}
$.fn.modal.defaults = {
backdrop: true
, keyboard: true
}
$.fn.modal.Constructor = Modal
/* MODAL DATA-API
* ============== */
$(function () {
$('body').on('click.modal.data-api', '[data-toggle="modal"]', function ( e ) {
var $this = $(this), href
, $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
, option = $target.data('modal') ? 'toggle' : $.extend({}, $target.data(), $this.data())
e.preventDefault()
$target.modal(option)
})
})
}( window.jQuery )

View file

@ -0,0 +1,96 @@
/* ===========================================================
* bootstrap-popover.js v2.0.0
* http://twitter.github.com/bootstrap/javascript.html#popovers
* ===========================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* =========================================================== */
!function( $ ) {
"use strict"
var Popover = function ( element, options ) {
this.init('popover', element, options)
}
/* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js
========================================== */
Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, {
constructor: Popover
, setContent: function () {
var $tip = this.tip()
, title = this.getTitle()
, content = this.getContent()
$tip.find('.popover-title')[ $.type(title) == 'object' ? 'append' : 'html' ](title)
$tip.find('.popover-content > *')[ $.type(content) == 'object' ? 'append' : 'html' ](content)
$tip.removeClass('fade top bottom left right in')
}
, hasContent: function () {
return this.getTitle() || this.getContent()
}
, getContent: function () {
var content
, $e = this.$element
, o = this.options
content = $e.attr('data-content')
|| (typeof o.content == 'function' ? o.content($e[0]) : o.content)
// || (typeof o.content == 'function' ? o.content.call($e[0]) : o.content)
content = content.toString().replace(/(^\s*|\s*$)/, "")
return content
}
, tip: function() {
if (!this.$tip) {
this.$tip = $(this.options.template)
}
return this.$tip
}
})
/* POPOVER PLUGIN DEFINITION
* ======================= */
$.fn.popover = function ( option ) {
return this.each(function () {
var $this = $(this)
, data = $this.data('popover')
, options = typeof option == 'object' && option
if (!data) $this.data('popover', (data = new Popover(this, options)))
if (typeof option == 'string') data[option]()
})
}
$.fn.popover.Constructor = Popover
$.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, {
placement: 'right'
, content: ''
, template: '<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"><p></p></div></div></div>'
})
}( window.jQuery )

View file

@ -0,0 +1,130 @@
/* ========================================================
* bootstrap-tab.js v2.0.0
* http://twitter.github.com/bootstrap/javascript.html#tabs
* ========================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ======================================================== */
!function( $ ){
"use strict"
/* TAB CLASS DEFINITION
* ==================== */
var Tab = function ( element ) {
this.element = $(element)
}
Tab.prototype = {
constructor: Tab
, show: function () {
var $this = this.element
, $ul = $this.closest('ul:not(.dropdown-menu)')
, selector = $this.attr('data-target')
, previous
, $target
if (!selector) {
selector = $this.attr('href')
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
}
if ( $this.parent('li').hasClass('active') ) return
previous = $ul.find('.active a').last()[0]
$this.trigger({
type: 'show'
, relatedTarget: previous
})
$target = $(selector)
this.activate($this.parent('li'), $ul)
this.activate($target, $target.parent(), function () {
$this.trigger({
type: 'shown'
, relatedTarget: previous
})
})
}
, activate: function ( element, container, callback) {
var $active = container.find('> .active')
, transition = callback
&& $.support.transition
&& $active.hasClass('fade')
function next() {
$active
.removeClass('active')
.find('> .dropdown-menu > .active')
.removeClass('active')
element.addClass('active')
if (transition) {
element[0].offsetWidth // reflow for transition
element.addClass('in')
} else {
element.removeClass('fade')
}
if ( element.parent('.dropdown-menu') ) {
element.closest('li.dropdown').addClass('active')
}
callback && callback()
}
transition ?
$active.one($.support.transition.end, next) :
next()
$active.removeClass('in')
}
}
/* TAB PLUGIN DEFINITION
* ===================== */
$.fn.tab = function ( option ) {
return this.each(function () {
var $this = $(this)
, data = $this.data('tab')
if (!data) $this.data('tab', (data = new Tab(this)))
if (typeof option == 'string') data[option]()
})
}
$.fn.tab.Constructor = Tab
/* TAB DATA-API
* ============ */
$(function () {
$('body').on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
e.preventDefault()
$(this).tab('show')
})
})
}( window.jQuery )

View file

@ -0,0 +1,271 @@
/* ===========================================================
* bootstrap-tooltip.js v2.0.0
* http://twitter.github.com/bootstrap/javascript.html#tooltips
* Inspired by the original jQuery.tipsy by Jason Frame
* ===========================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================== */
!function( $ ) {
"use strict"
/* TOOLTIP PUBLIC CLASS DEFINITION
* =============================== */
var Tooltip = function ( element, options ) {
this.init('tooltip', element, options)
}
Tooltip.prototype = {
constructor: Tooltip
, init: function ( type, element, options ) {
var eventIn
, eventOut
this.type = type
this.$element = $(element)
this.options = this.getOptions(options)
this.enabled = true
if (this.options.trigger != 'manual') {
eventIn = this.options.trigger == 'hover' ? 'mouseenter' : 'focus'
eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur'
this.$element.on(eventIn, this.options.selector, $.proxy(this.enter, this))
this.$element.on(eventOut, this.options.selector, $.proxy(this.leave, this))
}
this.options.selector ?
(this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
this.fixTitle()
}
, getOptions: function ( options ) {
options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data())
if (options.delay && typeof options.delay == 'number') {
options.delay = {
show: options.delay
, hide: options.delay
}
}
return options
}
, enter: function ( e ) {
var self = $(e.currentTarget)[this.type](this._options).data(this.type)
if (!self.options.delay || !self.options.delay.show) {
self.show()
} else {
self.hoverState = 'in'
setTimeout(function() {
if (self.hoverState == 'in') {
self.show()
}
}, self.options.delay.show)
}
}
, leave: function ( e ) {
var self = $(e.currentTarget)[this.type](this._options).data(this.type)
if (!self.options.delay || !self.options.delay.hide) {
self.hide()
} else {
self.hoverState = 'out'
setTimeout(function() {
if (self.hoverState == 'out') {
self.hide()
}
}, self.options.delay.hide)
}
}
, show: function () {
var $tip
, inside
, pos
, actualWidth
, actualHeight
, placement
, tp
if (this.hasContent() && this.enabled) {
$tip = this.tip()
this.setContent()
if (this.options.animation) {
$tip.addClass('fade')
}
placement = typeof this.options.placement == 'function' ?
this.options.placement.call(this, $tip[0], this.$element[0]) :
this.options.placement
inside = /in/.test(placement)
$tip
.remove()
.css({ top: 0, left: 0, display: 'block' })
.appendTo(inside ? this.$element : document.body)
pos = this.getPosition(inside)
actualWidth = $tip[0].offsetWidth
actualHeight = $tip[0].offsetHeight
switch (inside ? placement.split(' ')[1] : placement) {
case 'bottom':
tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
break
case 'top':
tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
break
case 'left':
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
break
case 'right':
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
break
}
$tip
.css(tp)
.addClass(placement)
.addClass('in')
}
}
, setContent: function () {
var $tip = this.tip()
$tip.find('.tooltip-inner').html(this.getTitle())
$tip.removeClass('fade in top bottom left right')
}
, hide: function () {
var that = this
, $tip = this.tip()
$tip.removeClass('in')
function removeWithAnimation() {
var timeout = setTimeout(function () {
$tip.off($.support.transition.end).remove()
}, 500)
$tip.one($.support.transition.end, function () {
clearTimeout(timeout)
$tip.remove()
})
}
$.support.transition && this.$tip.hasClass('fade') ?
removeWithAnimation() :
$tip.remove()
}
, fixTitle: function () {
var $e = this.$element
if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
$e.attr('data-original-title', $e.attr('title') || '').removeAttr('title')
}
}
, hasContent: function () {
return this.getTitle()
}
, getPosition: function (inside) {
return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), {
width: this.$element[0].offsetWidth
, height: this.$element[0].offsetHeight
})
}
, getTitle: function () {
var title
, $e = this.$element
, o = this.options
title = $e.attr('data-original-title')
|| (typeof o.title == 'function' ? o.title($e[0]) : o.title)
// || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
title = title.toString().replace(/(^\s*|\s*$)/, "")
return title
}
, tip: function () {
return this.$tip = this.$tip || $(this.options.template)
}
, validate: function () {
if (!this.$element[0].parentNode) {
this.hide()
this.$element = null
this.options = null
}
}
, enable: function () {
this.enabled = true
}
, disable: function () {
this.enabled = false
}
, toggleEnabled: function () {
this.enabled = !this.enabled
}
, toggle: function () {
this[this.tip().hasClass('in') ? 'hide' : 'show']()
}
}
/* TOOLTIP PLUGIN DEFINITION
* ========================= */
$.fn.tooltip = function ( option ) {
return this.each(function () {
var $this = $(this)
, data = $this.data('tooltip')
, options = typeof option == 'object' && option
if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
if (typeof option == 'string') data[option]()
})
}
$.fn.tooltip.Constructor = Tooltip
$.fn.tooltip.defaults = {
animation: true
, delay: 0
, selector: false
, placement: 'top'
, trigger: 'hover'
, title: ''
, template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
}
}( window.jQuery )

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,353 @@
/*
jQuery Tags Input Plugin 1.3.3
Copyright (c) 2011 XOXCO, Inc
Documentation for this plugin lives here:
http://xoxco.com/clickable/jquery-tags-input
Licensed under the MIT license:
http://www.opensource.org/licenses/mit-license.php
ben@xoxco.com
*/
(function($) {
var delimiter = new Array();
var tags_callbacks = new Array();
$.fn.doAutosize = function(o){
var minWidth = $(this).data('minwidth'),
maxWidth = $(this).data('maxwidth'),
val = '',
input = $(this),
testSubject = $('#'+$(this).data('tester_id'));
if (val === (val = input.val())) {return;}
// Enter new content into testSubject
var escaped = val.replace(/&/g, '&amp;').replace(/\s/g,' ').replace(/</g, '&lt;').replace(/>/g, '&gt;');
testSubject.html(escaped);
// Calculate new width + whether to change
var testerWidth = testSubject.width(),
newWidth = (testerWidth + o.comfortZone) >= minWidth ? testerWidth + o.comfortZone : minWidth,
currentWidth = input.width(),
isValidWidthChange = (newWidth < currentWidth && newWidth >= minWidth)
|| (newWidth > minWidth && newWidth < maxWidth);
// Animate width
if (isValidWidthChange) {
input.width(newWidth);
}
};
$.fn.resetAutosize = function(options){
// alert(JSON.stringify(options));
var minWidth = $(this).data('minwidth') || options.minInputWidth || $(this).width(),
maxWidth = $(this).data('maxwidth') || options.maxInputWidth || ($(this).closest('.tagsinput').width() - options.inputPadding),
val = '',
input = $(this),
testSubject = $('<tester/>').css({
position: 'absolute',
top: -9999,
left: -9999,
width: 'auto',
fontSize: input.css('fontSize'),
fontFamily: input.css('fontFamily'),
fontWeight: input.css('fontWeight'),
letterSpacing: input.css('letterSpacing'),
whiteSpace: 'nowrap'
}),
testerId = $(this).attr('id')+'_autosize_tester';
if(! $('#'+testerId).length > 0){
testSubject.attr('id', testerId);
testSubject.appendTo('body');
}
input.data('minwidth', minWidth);
input.data('maxwidth', maxWidth);
input.data('tester_id', testerId);
input.css('width', minWidth);
};
$.fn.addTag = function(value,options) {
options = jQuery.extend({focus:false,callback:true},options);
this.each(function() {
var id = $(this).attr('id');
var tagslist = $(this).val().split(delimiter[id]);
if (tagslist[0] == '') {
tagslist = new Array();
}
value = jQuery.trim(value);
if (options.unique) {
var skipTag = $(tagslist).tagExist(value);
if(skipTag == true) {
//Marks fake input as not_valid to let styling it
$('#'+id+'_tag').addClass('not_valid');
}
} else {
var skipTag = false;
}
if (value !='' && skipTag != true) {
$('<span>').addClass('tag').append(
$('<span>').text(value).append('&nbsp;&nbsp;'),
$('<a>', {
href : '#',
title : 'Removing tag',
text : 'x'
}).click(function () {
return $('#' + id).removeTag(escape(value));
})
).insertBefore('#' + id + '_addTag');
tagslist.push(value);
$('#'+id+'_tag').val('');
if (options.focus) {
$('#'+id+'_tag').focus();
} else {
$('#'+id+'_tag').blur();
}
$.fn.tagsInput.updateTagsField(this,tagslist);
if (options.callback && tags_callbacks[id] && tags_callbacks[id]['onAddTag']) {
var f = tags_callbacks[id]['onAddTag'];
f.call(this, value);
}
if(tags_callbacks[id] && tags_callbacks[id]['onChange'])
{
var i = tagslist.length;
var f = tags_callbacks[id]['onChange'];
f.call(this, $(this), tagslist[i-1]);
}
}
});
return false;
};
$.fn.removeTag = function(value) {
value = unescape(value);
this.each(function() {
var id = $(this).attr('id');
var old = $(this).val().split(delimiter[id]);
$('#'+id+'_tagsinput .tag').remove();
str = '';
for (i=0; i< old.length; i++) {
if (old[i]!=value) {
str = str + delimiter[id] +old[i];
}
}
$.fn.tagsInput.importTags(this,str);
if (tags_callbacks[id] && tags_callbacks[id]['onRemoveTag']) {
var f = tags_callbacks[id]['onRemoveTag'];
f.call(this, value);
}
});
return false;
};
$.fn.tagExist = function(val) {
return (jQuery.inArray(val, $(this)) >= 0); //true when tag exists, false when not
};
// clear all existing tags and import new ones from a string
$.fn.importTags = function(str) {
id = $(this).attr('id');
$('#'+id+'_tagsinput .tag').remove();
$.fn.tagsInput.importTags(this,str);
}
$.fn.tagsInput = function(options) {
var settings = jQuery.extend({
interactive:true,
defaultText:'add a tag',
minChars:0,
width:'300px',
height:'100px',
autocomplete: {selectFirst: false },
'hide':true,
'delimiter':',',
'unique':true,
removeWithBackspace:true,
placeholderColor:'#666666',
autosize: true,
comfortZone: 20,
inputPadding: 6*2
},options);
this.each(function() {
if (settings.hide) {
$(this).hide();
}
var id = $(this).attr('id')
var data = jQuery.extend({
pid:id,
real_input: '#'+id,
holder: '#'+id+'_tagsinput',
input_wrapper: '#'+id+'_addTag',
fake_input: '#'+id+'_tag'
},settings);
delimiter[id] = data.delimiter;
if (settings.onAddTag || settings.onRemoveTag || settings.onChange) {
tags_callbacks[id] = new Array();
tags_callbacks[id]['onAddTag'] = settings.onAddTag;
tags_callbacks[id]['onRemoveTag'] = settings.onRemoveTag;
tags_callbacks[id]['onChange'] = settings.onChange;
}
var markup = '<div id="'+id+'_tagsinput" class="tagsinput"><div id="'+id+'_addTag">';
if (settings.interactive) {
markup = markup + '<input id="'+id+'_tag" value="" data-default="'+settings.defaultText+'" />';
}
markup = markup + '</div><div class="tags_clear"></div></div>';
$(markup).insertAfter(this);
$(data.holder).css('width',settings.width);
$(data.holder).css('height',settings.height);
if ($(data.real_input).val()!='') {
$.fn.tagsInput.importTags($(data.real_input),$(data.real_input).val());
}
if (settings.interactive) {
$(data.fake_input).val($(data.fake_input).attr('data-default'));
$(data.fake_input).css('color',settings.placeholderColor);
$(data.fake_input).resetAutosize(settings);
$(data.holder).bind('click',data,function(event) {
$(event.data.fake_input).focus();
});
$(data.fake_input).bind('focus',data,function(event) {
if ($(event.data.fake_input).val()==$(event.data.fake_input).attr('data-default')) {
$(event.data.fake_input).val('');
}
$(event.data.fake_input).css('color','#000000');
});
if (settings.autocomplete_url != undefined) {
// 2012-02-23 me
// autocomplete_options = {source: settings.autocomplete_url};
autocomplete_options = settings.auto;
// 2012-02-23 me
for (attrname in settings.autocomplete) {
autocomplete_options[attrname] = settings.autocomplete[attrname];
}
if (jQuery.Autocompleter !== undefined) {
$(data.fake_input).autocomplete(settings.autocomplete_url, settings.autocomplete);
$(data.fake_input).bind('result',data,function(event,data,formatted) {
if (data) {
$('#'+id).addTag(data[0] + "",{focus:true,unique:(settings.unique)});
}
});
} else if (jQuery.ui.autocomplete !== undefined) {
$(data.fake_input).autocomplete(autocomplete_options);
$(data.fake_input).bind('autocompleteselect',data,function(event,ui) {
$(event.data.real_input).addTag(ui.item.value,{focus:true,unique:(settings.unique)});
return false;
});
}
} else {
// if a user tabs out of the field, create a new tag
// this is only available if autocomplete is not used.
$(data.fake_input).bind('blur',data,function(event) {
var d = $(this).attr('data-default');
if ($(event.data.fake_input).val()!='' && $(event.data.fake_input).val()!=d) {
if( (event.data.minChars <= $(event.data.fake_input).val().length) && (!event.data.maxChars || (event.data.maxChars >= $(event.data.fake_input).val().length)) )
$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:true,unique:(settings.unique)});
} else {
$(event.data.fake_input).val($(event.data.fake_input).attr('data-default'));
$(event.data.fake_input).css('color',settings.placeholderColor);
}
return false;
});
}
// if user types a comma, create a new tag
$(data.fake_input).bind('keypress',data,function(event) {
if (event.which==event.data.delimiter.charCodeAt(0) || event.which==13 ) {
event.preventDefault();
if( (event.data.minChars <= $(event.data.fake_input).val().length) && (!event.data.maxChars || (event.data.maxChars >= $(event.data.fake_input).val().length)) )
$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:true,unique:(settings.unique)});
$(event.data.fake_input).resetAutosize(settings);
return false;
} else if (event.data.autosize) {
$(event.data.fake_input).doAutosize(settings);
}
});
//Delete last tag on backspace
data.removeWithBackspace && $(data.fake_input).bind('keydown', function(event)
{
if(event.keyCode == 8 && $(this).val() == '')
{
event.preventDefault();
var last_tag = $(this).closest('.tagsinput').find('.tag:last').text();
var id = $(this).attr('id').replace(/_tag$/, '');
last_tag = last_tag.replace(/[\s]+x$/, '');
$('#' + id).removeTag(escape(last_tag));
$(this).trigger('focus');
}
});
$(data.fake_input).blur();
//Removes the not_valid class when user changes the value of the fake input
if(data.unique) {
$(data.fake_input).keydown(function(event){
if(event.keyCode == 8 || String.fromCharCode(event.which).match(/\w+|[áéíóúÁÉÍÓÚñÑ,/]+/)) {
$(this).removeClass('not_valid');
}
});
}
} // if settings.interactive
return false;
});
return this;
};
$.fn.tagsInput.updateTagsField = function(obj,tagslist) {
var id = $(obj).attr('id');
$(obj).val(tagslist.join(delimiter[id]));
};
$.fn.tagsInput.importTags = function(obj,val) {
$(obj).val('');
var id = $(obj).attr('id');
var tags = val.split(delimiter[id]);
for (i=0; i<tags.length; i++) {
$(obj).addTag(tags[i],{focus:false,callback:false});
}
if(tags_callbacks[id] && tags_callbacks[id]['onChange'])
{
var f = tags_callbacks[id]['onChange'];
f.call(obj, obj, tags[i]);
}
};
})(jQuery);

View file

@ -0,0 +1,206 @@
Spine ?= require('spine')
$ = Spine.$
Model = Spine.Model
Ajax =
getURL: (object) ->
object and object.url?() or object.url
enabled: true
pending: false
requests: []
disable: (callback) ->
if @enabled
@enabled = false
do callback
@enabled = true
else
do callback
requestNext: ->
next = @requests.shift()
if next
@request(next)
else
@pending = false
request: (callback) ->
(do callback).complete(=> do @requestNext)
queue: (callback) ->
return unless @enabled
if @pending
@requests.push(callback)
else
@pending = true
@request(callback)
callback
class Base
defaults:
contentType: 'application/json'
dataType: 'json'
processData: false
headers: {'X-Requested-With': 'XMLHttpRequest'}
ajax: (params, defaults) ->
$.ajax($.extend({}, @defaults, defaults, params))
queue: (callback) ->
Ajax.queue(callback)
class Collection extends Base
constructor: (@model) ->
find: (id, params) ->
record = new @model(id: id)
@ajax(
params,
type: 'GET',
url: Ajax.getURL(record)
).success(@recordsResponse)
.error(@errorResponse)
all: (params) ->
@ajax(
params,
type: 'GET',
url: Ajax.getURL(@model)
).success(@recordsResponse)
.error(@errorResponse)
fetch: (params = {}, options = {}) ->
if id = params.id
delete params.id
@find(id, params).success (record) =>
@model.refresh(record, options)
else
@all(params).success (records) =>
@model.refresh(records, options)
# Private
recordsResponse: (data, status, xhr) =>
@model.trigger('ajaxSuccess', null, status, xhr)
errorResponse: (xhr, statusText, error) =>
@model.trigger('ajaxError', null, xhr, statusText, error)
class Singleton extends Base
constructor: (@record) ->
@model = @record.constructor
reload: (params, options) ->
@queue =>
@ajax(
params,
type: 'GET'
url: Ajax.getURL(@record)
).success(@recordResponse(options))
.error(@errorResponse(options))
create: (params, options) ->
@queue =>
@ajax(
params,
type: 'POST'
data: JSON.stringify(@record)
url: Ajax.getURL(@model)
).success(@recordResponse(options))
.error(@errorResponse(options))
update: (params, options) ->
@queue =>
@ajax(
params,
type: 'PUT'
data: JSON.stringify(@record)
url: Ajax.getURL(@record)
).success(@recordResponse(options))
.error(@errorResponse(options))
destroy: (params, options) ->
@queue =>
@ajax(
params,
type: 'DELETE'
url: Ajax.getURL(@record)
).success(@recordResponse(options))
.error(@errorResponse(options))
# Private
recordResponse: (options = {}) =>
(data, status, xhr) =>
if Spine.isBlank(data)
data = false
else
data = @model.fromJSON(data)
Ajax.disable =>
if data
# ID change, need to do some shifting
if data.id and @record.id isnt data.id
@record.changeID(data.id)
# Update with latest data
@record.updateAttributes(data.attributes())
@record.trigger('ajaxSuccess', data, status, xhr)
# 2012-02-23 me
# options.success?.apply(@record)
options.success?(@record)
errorResponse: (options = {}) =>
(xhr, statusText, error) =>
@record.trigger('ajaxError', xhr, statusText, error)
options.error?.apply(@record)
# Ajax endpoint
Model.host = ''
Include =
ajax: -> new Singleton(this)
url: (args...) ->
url = Ajax.getURL(@constructor)
url += '/' unless url.charAt(url.length - 1) is '/'
url += encodeURIComponent(@id)
args.unshift(url)
args.join('/')
Extend =
ajax: -> new Collection(this)
url: (args...) ->
args.unshift(@className.toLowerCase() + 's')
args.unshift(Model.host)
args.join('/')
Model.Ajax =
extended: ->
@fetch @ajaxFetch
@change @ajaxChange
@extend Extend
@include Include
# Private
ajaxFetch: ->
@ajax().fetch(arguments...)
ajaxChange: (record, type, options = {}) ->
return if options.ajax is false
record.ajax()[type](options.ajax, options)
Model.Ajax.Methods =
extended: ->
@extend Extend
@include Include
# Globals
Ajax.defaults = Base::defaults
Spine.Ajax = Ajax
module?.exports = Ajax

View file

@ -0,0 +1,42 @@
Spine ?= require('spine')
$ = Spine.$
class Spine.List extends Spine.Controller
events:
'click .item': 'click'
selectFirst: false
constructor: ->
super
@bind 'change', @change
template: -> arguments[0]
change: (item) =>
@current = item
unless @current
@children().removeClass('active')
return
@children().removeClass('active')
@children().forItem(@current).addClass('active')
render: (items) ->
@items = items if items
@html @template(@items)
@change @current
if @selectFirst
unless @children('.active').length
@children(':first').click()
children: (sel) ->
@el.children(sel)
click: (e) ->
item = $(e.currentTarget).item()
@trigger('change', item)
true
module?.exports = Spine.List

View file

@ -0,0 +1,16 @@
Spine ?= require('spine')
Spine.Model.Local =
extended: ->
@change @saveLocal
@fetch @loadLocal
saveLocal: ->
result = JSON.stringify(@)
localStorage[@className] = result
loadLocal: ->
result = localStorage[@className]
@refresh(result or [], clear: true)
module?.exports = Spine.Model.Local

View file

@ -0,0 +1,85 @@
Spine ?= require('spine')
$ = Spine.$
class Spine.Manager extends Spine.Module
@include Spine.Events
constructor: ->
@controllers = []
@bind 'change', @change
@add(arguments...)
add: (controllers...) ->
@addOne(cont) for cont in controllers
addOne: (controller) ->
controller.bind 'active', (args...) =>
@trigger('change', controller, args...)
controller.bind 'release', =>
@controllers.splice(@controllers.indexOf(controller), 1)
@controllers.push(controller)
deactivate: ->
@trigger('change', false, arguments...)
# Private
change: (current, args...) ->
for cont in @controllers
if cont is current
cont.activate(args...)
else
cont.deactivate(args...)
Spine.Controller.include
active: (args...) ->
if typeof args[0] is 'function'
@bind('active', args[0])
else
args.unshift('active')
@trigger(args...)
@
isActive: ->
@el.hasClass('active')
activate: ->
@el.addClass('active')
@
deactivate: ->
@el.removeClass('active')
@
class Spine.Stack extends Spine.Controller
controllers: {}
routes: {}
className: 'spine stack'
constructor: ->
super
@manager = new Spine.Manager
@manager.bind 'change', (controller, args...) =>
@active(args...) if controller
for key, value of @controllers
@[key] = new value(stack: @)
@add(@[key])
for key, value of @routes
do (key, value) =>
callback = value if typeof value is 'function'
callback or= => @[value].active(arguments...)
@route(key, callback)
@[@default].active() if @default
add: (controller) ->
@manager.add(controller)
@append(controller)
module?.exports = Spine.Manager

View file

@ -0,0 +1,144 @@
Spine ?= require('spine')
isArray = Spine.isArray
require ?= ((value) -> eval(value))
class Collection extends Spine.Module
constructor: (options = {}) ->
for key, value of options
@[key] = value
all: ->
@model.select (rec) => @associated(rec)
first: ->
@all()[0]
last: ->
values = @all()
values[values.length - 1]
find: (id) ->
records = @select (rec) =>
rec.id + '' is id + ''
throw('Unknown record') unless records[0]
records[0]
findAllByAttribute: (name, value) ->
@model.select (rec) =>
rec[name] is value
findByAttribute: (name, value) ->
@findAllByAttribute(name, value)[0]
select: (cb) ->
@model.select (rec) =>
@associated(rec) and cb(rec)
refresh: (values) ->
delete @model.records[record.id] for record in @all()
records = @model.fromJSON(values)
records = [records] unless isArray(records)
for record in records
record.newRecord = false
record[@fkey] = @record.id
@model.records[record.id] = record
@model.trigger('refresh', records)
create: (record) ->
record[@fkey] = @record.id
@model.create(record)
# Private
associated: (record) ->
record[@fkey] is @record.id
class Instance extends Spine.Module
constructor: (options = {}) ->
for key, value of options
@[key] = value
exists: ->
@record[@fkey] and @model.exists(@record[@fkey])
update: (value) ->
unless value instanceof @model
value = new @model(value)
value.save() if value.isNew()
@record[@fkey] = value and value.id
class Singleton extends Spine.Module
constructor: (options = {}) ->
for key, value of options
@[key] = value
find: ->
@record.id and @model.findByAttribute(@fkey, @record.id)
update: (value) ->
unless value instanceof @model
value = @model.fromJSON(value)
value[@fkey] = @record.id
value.save()
singularize = (str) ->
str.replace(/s$/, '')
underscore = (str) ->
str.replace(/::/g, '/')
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
.replace(/([a-z\d])([A-Z])/g, '$1_$2')
.replace(/-/g, '_')
.toLowerCase()
Spine.Model.extend
hasMany: (name, model, fkey) ->
fkey ?= "#{underscore(this.className)}_id"
association = (record) ->
model = require(model) if typeof model is 'string'
new Collection(
name: name, model: model,
record: record, fkey: fkey
)
@::[name] = (value) ->
association(@).refresh(value) if value?
association(@)
belongsTo: (name, model, fkey) ->
fkey ?= "#{singularize(name)}_id"
association = (record) ->
model = require(model) if typeof model is 'string'
new Instance(
name: name, model: model,
record: record, fkey: fkey
)
@::[name] = (value) ->
association(@).update(value) if value?
association(@).exists()
@attributes.push(fkey)
hasOne: (name, model, fkey) ->
fkey ?= "#{underscore(@className)}_id"
association = (record) ->
model = require(model) if typeof model is 'string'
new Singleton(
name: name, model: model,
record: record, fkey: fkey
)
@::[name] = (value) ->
association(@).update(value) if value?
association(@).find()

View file

@ -0,0 +1,147 @@
Spine ?= require('spine')
$ = Spine.$
hashStrip = /^#*/
namedParam = /:([\w\d]+)/g
splatParam = /\*([\w\d]+)/g
escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g
class Spine.Route extends Spine.Module
@extend Spine.Events
@historySupport: window.history?.pushState?
@routes: []
@options:
trigger: true
history: false
shim: false
@add: (path, callback) ->
if (typeof path is 'object' and path not instanceof RegExp)
@add(key, value) for key, value of path
else
@routes.push(new @(path, callback))
@setup: (options = {}) ->
@options = $.extend({}, @options, options)
if (@options.history)
@history = @historySupport && @options.history
return if @options.shim
if @history
$(window).bind('popstate', @change)
else
$(window).bind('hashchange', @change)
@change()
@unbind: ->
if @history
$(window).unbind('popstate', @change)
else
$(window).unbind('hashchange', @change)
@navigate: (args...) ->
options = {}
lastArg = args[args.length - 1]
if typeof lastArg is 'object'
options = args.pop()
else if typeof lastArg is 'boolean'
options.trigger = args.pop()
options = $.extend({}, @options, options)
path = args.join('/')
return if @path is path
@path = path
@trigger('navigate', @path)
@matchRoute(@path, options) if options.trigger
return if options.shim
if @history
history.pushState(
{},
document.title,
@path
)
else
window.location.hash = @path
# Private
@getPath: ->
path = window.location.pathname
if path.substr(0,1) isnt '/'
path = '/' + path
path
@getHash: -> window.location.hash
@getFragment: -> @getHash().replace(hashStrip, '')
@getHost: ->
(document.location + '').replace(@getPath() + @getHash(), '')
@change: ->
# change me 2012-02-15
# path = if @getFragment() isnt '' then @getFragment() else @getPath()
path = if @getFragment() isnt 'workaround' then @getFragment() else @getPath()
return if path is @path
@path = path
@matchRoute(@path)
@matchRoute: (path, options) ->
for route in @routes
if route.match(path, options)
@trigger('change', route, path)
return route
constructor: (@path, @callback) ->
@names = []
if typeof path is 'string'
namedParam.lastIndex = 0
while (match = namedParam.exec(path)) != null
@names.push(match[1])
path = path.replace(escapeRegExp, '\\$&')
.replace(namedParam, '([^\/]*)')
.replace(splatParam, '(.*?)')
@route = new RegExp('^' + path + '$')
else
@route = path
match: (path, options = {}) ->
match = @route.exec(path)
return false unless match
options.match = match
params = match.slice(1)
if @names.length
for param, i in params
options[@names[i]] = param
@callback.call(null, options) isnt false
# Coffee-script bug
Spine.Route.change = Spine.Route.proxy(Spine.Route.change)
Spine.Controller.include
route: (path, callback) ->
Spine.Route.add(path, @proxy(callback))
routes: (routes) ->
@route(key, value) for key, value of routes
navigate: ->
Spine.Route.navigate.apply(Spine.Route, arguments)
module?.exports = Spine.Route

View file

@ -0,0 +1,528 @@
Events =
bind: (ev, callback) ->
evs = ev.split(' ')
calls = @hasOwnProperty('_callbacks') and @_callbacks or= {}
for name in evs
calls[name] or= []
calls[name].push(callback)
this
one: (ev, callback) ->
@bind ev, ->
@unbind(ev, arguments.callee)
callback.apply(@, arguments)
trigger: (args...) ->
ev = args.shift()
list = @hasOwnProperty('_callbacks') and @_callbacks?[ev]
return unless list
for callback in list
if callback.apply(@, args) is false
break
true
unbind: (ev, callback) ->
unless ev
@_callbacks = {}
return this
list = @_callbacks?[ev]
return this unless list
unless callback
delete @_callbacks[ev]
return this
for cb, i in list when cb is callback
list = list.slice()
list.splice(i, 1)
@_callbacks[ev] = list
break
this
Log =
trace: true
logPrefix: '(App)'
log: (args...) ->
return unless @trace
if @logPrefix then args.unshift(@logPrefix)
console?.log?(args...)
this
moduleKeywords = ['included', 'extended']
class Module
@include: (obj) ->
throw('include(obj) requires obj') unless obj
for key, value of obj when key not in moduleKeywords
@::[key] = value
obj.included?.apply(@)
this
@extend: (obj) ->
throw('extend(obj) requires obj') unless obj
for key, value of obj when key not in moduleKeywords
@[key] = value
obj.extended?.apply(@)
this
@proxy: (func) ->
=> func.apply(@, arguments)
proxy: (func) ->
=> func.apply(@, arguments)
constructor: ->
@init?(arguments...)
class Model extends Module
@extend Events
@records: {}
@crecords: {}
@attributes: []
@configure: (name, attributes...) ->
@className = name
@records = {}
@crecords = {}
@attributes = attributes if attributes.length
@attributes and= makeArray(@attributes)
@attributes or= []
@unbind()
this
@toString: -> "#{@className}(#{@attributes.join(", ")})"
@find: (id) ->
record = @records[id]
if !record and ("#{id}").match(/c-\d+/)
return @findCID(id)
throw('Unknown record'+id+@className) unless record
record.clone()
@findCID: (cid) ->
record = @crecords[cid]
throw('Unknown record') unless record
record.clone()
@exists: (id) ->
try
return @find(id)
catch e
return false
@refresh: (values, options = {}) ->
if options.clear
@records = {}
@crecords = {}
records = @fromJSON(values)
records = [records] unless isArray(records)
for record in records
record.id or= record.cid
@records[record.id] = record
@crecords[record.cid] = record
@trigger('refresh', not options.clear and @cloneArray(records))
this
@select: (callback) ->
result = (record for id, record of @records when callback(record))
@cloneArray(result)
@findByAttribute: (name, value) ->
for id, record of @records
if record[name] is value
return record.clone()
null
@findAllByAttribute: (name, value) ->
@select (item) ->
item[name] is value
@each: (callback) ->
for key, value of @records
callback(value.clone())
@all: ->
@cloneArray(@recordsValues())
@first: ->
record = @recordsValues()[0]
record?.clone()
@last: ->
values = @recordsValues()
record = values[values.length - 1]
record?.clone()
@count: ->
@recordsValues().length
@deleteAll: ->
for key, value of @records
delete @records[key]
@destroyAll: ->
for key, value of @records
@records[key].destroy()
@update: (id, atts, options) ->
@find(id).updateAttributes(atts, options)
@create: (atts, options) ->
record = new @(atts)
record.save(options)
@destroy: (id, options) ->
@find(id).destroy(options)
@change: (callbackOrParams) ->
if typeof callbackOrParams is 'function'
@bind('change', callbackOrParams)
else
@trigger('change', callbackOrParams)
@fetch: (callbackOrParams) ->
if typeof callbackOrParams is 'function'
@bind('fetch', callbackOrParams)
else
@trigger('fetch', callbackOrParams)
@toJSON: ->
@recordsValues()
@fromJSON: (objects) ->
return unless objects
if typeof objects is 'string'
objects = JSON.parse(objects)
if isArray(objects)
(new @(value) for value in objects)
else
new @(objects)
@fromForm: ->
(new this).fromForm(arguments...)
# Private
@recordsValues: ->
result = []
for key, value of @records
result.push(value)
result
@cloneArray: (array) ->
(value.clone() for value in array)
@idCounter: 0
@uid: ->
@idCounter++
# Instance
constructor: (atts) ->
super
@load atts if atts
@cid or= 'c-' + @constructor.uid()
isNew: ->
not @exists()
isValid: ->
not @validate()
validate: ->
load: (atts) ->
for key, value of atts
if typeof @[key] is 'function'
@[key](value)
else
@[key] = value
this
attributes: ->
result = {}
for key in @constructor.attributes when key of this
if typeof @[key] is 'function'
result[key] = @[key]()
else
result[key] = @[key]
result.id = @id if @id
result
eql: (rec) ->
!!(rec and rec.constructor is @constructor and
((rec.id and rec.id is @id) or rec.cid is @cid))
save: (options = {}) ->
unless options.validate is false
error = @validate()
if error
@trigger('error', error)
return false
@trigger('beforeSave', options)
record = if @isNew() then @create(options) else @update(options)
@trigger('save', options)
record
updateAttribute: (name, value) ->
@[name] = value
@save()
updateAttributes: (atts, options) ->
@load(atts)
@save(options)
changeID: (id) ->
records = @constructor.records
records[id] = records[@id]
delete records[@id]
@id = id
@save()
destroy: (options = {}) ->
@trigger('beforeDestroy', options)
delete @constructor.records[@id]
delete @constructor.crecords[@cid]
@destroyed = true
@trigger('destroy', options)
@trigger('change', 'destroy', options)
@unbind()
this
dup: (newRecord) ->
result = new @constructor(@attributes())
if newRecord is false
result.cid = @cid
else
delete result.id
result
clone: ->
Object.create(@)
reload: ->
return this if @isNew()
original = @constructor.find(@id)
@load(original.attributes())
original
toJSON: ->
@attributes()
toString: ->
"<#{@constructor.className} (#{JSON.stringify(@)})>"
fromForm: (form) ->
result = {}
for key in $(form).serializeArray()
result[key.name] = key.value
@load(result)
exists: ->
@id && @id of @constructor.records
# Private
update: (options) ->
@trigger('beforeUpdate', options)
records = @constructor.records
records[@id].load @attributes()
clone = records[@id].clone()
clone.trigger('update', options)
clone.trigger('change', 'update', options)
clone
create: (options) ->
@trigger('beforeCreate', options)
@id = @cid unless @id
record = @dup(false)
@constructor.records[@id] = record
@constructor.crecords[@cid] = record
clone = record.clone()
clone.trigger('create', options)
clone.trigger('change', 'create', options)
clone
bind: (events, callback) ->
@constructor.bind events, binder = (record) =>
if record && @eql(record)
callback.apply(@, arguments)
@constructor.bind 'unbind', unbinder = (record) =>
if record && @eql(record)
@constructor.unbind(events, binder)
@constructor.unbind('unbind', unbinder)
binder
one: (events, callback) ->
binder = @bind events, =>
@constructor.unbind(events, binder)
callback.apply(@)
trigger: (args...) ->
args.splice(1, 0, @)
@constructor.trigger(args...)
unbind: ->
@trigger('unbind')
class Controller extends Module
@include Events
@include Log
eventSplitter: /^(\S+)\s*(.*)$/
tag: 'div'
constructor: (options) ->
@options = options
for key, value of @options
@[key] = value
@el = document.createElement(@tag) unless @el
@el = $(@el)
@el.addClass(@className) if @className
@el.attr(@attributes) if @attributes
@release -> @el.remove()
@events = @constructor.events unless @events
@elements = @constructor.elements unless @elements
@delegateEvents() if @events
@refreshElements() if @elements
super
release: (callback) =>
if typeof callback is 'function'
@bind 'release', callback
else
@trigger 'release'
$: (selector) -> $(selector, @el)
delegateEvents: ->
for key, method of @events
unless typeof(method) is 'function'
method = @proxy(@[method])
match = key.match(@eventSplitter)
eventName = match[1]
selector = match[2]
if selector is ''
@el.bind(eventName, method)
else
@el.delegate(selector, eventName, method)
refreshElements: ->
for key, value of @elements
@[value] = @$(key)
delay: (func, timeout) ->
setTimeout(@proxy(func), timeout || 0)
html: (element) ->
@el.html(element.el or element)
@refreshElements()
@el
append: (elements...) ->
elements = (e.el or e for e in elements)
@el.append(elements...)
@refreshElements()
@el
appendTo: (element) ->
@el.appendTo(element.el or element)
@refreshElements()
@el
prepend: (elements...) ->
elements = (e.el or e for e in elements)
@el.prepend(elements...)
@refreshElements()
@el
replace: (element) ->
[previous, @el] = [@el, $(element.el or element)]
previous.replaceWith(@el)
@delegateEvents()
@refreshElements()
@el
# Utilities & Shims
$ = window?.jQuery or window?.Zepto or (element) -> element
unless typeof Object.create is 'function'
Object.create = (o) ->
Func = ->
Func.prototype = o
new Func()
isArray = (value) ->
Object::toString.call(value) is '[object Array]'
isBlank = (value) ->
return true unless value
return false for key of value
true
makeArray = (args) ->
Array.prototype.slice.call(args, 0)
# Globals
Spine = @Spine = {}
module?.exports = Spine
Spine.version = '1.0.6'
Spine.isArray = isArray
Spine.isBlank = isBlank
Spine.$ = $
Spine.Events = Events
Spine.Log = Log
Spine.Module = Module
Spine.Controller = Controller
Spine.Model = Model
# Global events
Module.extend.call(Spine, Events)
# JavaScript compatability
Module.create = Module.sub =
Controller.create = Controller.sub =
Model.sub = (instances, statics) ->
class result extends this
result.include(instances) if instances
result.extend(statics) if statics
result.unbind?()
result
Model.setup = (name, attributes = []) ->
class Instance extends this
Instance.configure(name, attributes...)
Instance
Module.init = Controller.init = Model.init = (a1, a2, a3, a4, a5) ->
new this(a1, a2, a3, a4, a5)
Spine.Class = Module

View file

@ -0,0 +1,36 @@
Spine ?= require('spine')
$ = Spine.$
class Spine.Tabs extends Spine.Controller
events:
'click [data-name]': 'click'
constructor: ->
super
@bind 'change', @change
change: (name) =>
return unless name
@current = name
@children().removeClass('active')
@children("[data-name=#{@current}]").addClass('active')
render: ->
@change @current
unless @children('.active').length or @current
@children(':first').click()
children: (sel) ->
@el.children(sel)
click: (e) ->
name = $(e.currentTarget).attr('data-name')
@trigger('change', name)
connect: (tabName, controller) ->
@bind 'change', (name) ->
controller.active() if name is tabName
controller.bind 'active', =>
@change tabName
module?.exports = Spine.Tabs

View file

@ -0,0 +1,15 @@
# jQuery.tmpl.js utilities
$ = jQuery ? require("jqueryify")
$.fn.item = ->
item = $(@)
item = item.data("item") or item.tmplItem?().data
item?.reload?()
item
$.fn.forItem = (item) ->
@filter ->
compare = $(@).item()
return item.eql?(compare) or item is compare

View file

@ -0,0 +1,611 @@
/*
* jQuery UI Accordion 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Accordion
*
* Depends:
* jquery.ui.core.js
* jquery.ui.widget.js
*/
(function( $, undefined ) {
$.widget( "ui.accordion", {
options: {
active: 0,
animated: "slide",
autoHeight: true,
clearStyle: false,
collapsible: false,
event: "click",
fillSpace: false,
header: "> li > :first-child,> :not(li):even",
icons: {
header: "ui-icon-triangle-1-e",
headerSelected: "ui-icon-triangle-1-s"
},
navigation: false,
navigationFilter: function() {
return this.href.toLowerCase() === location.href.toLowerCase();
}
},
_create: function() {
var self = this,
options = self.options;
self.running = 0;
self.element
.addClass( "ui-accordion ui-widget ui-helper-reset" )
// in lack of child-selectors in CSS
// we need to mark top-LIs in a UL-accordion for some IE-fix
.children( "li" )
.addClass( "ui-accordion-li-fix" );
self.headers = self.element.find( options.header )
.addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" )
.bind( "mouseenter.accordion", function() {
if ( options.disabled ) {
return;
}
$( this ).addClass( "ui-state-hover" );
})
.bind( "mouseleave.accordion", function() {
if ( options.disabled ) {
return;
}
$( this ).removeClass( "ui-state-hover" );
})
.bind( "focus.accordion", function() {
if ( options.disabled ) {
return;
}
$( this ).addClass( "ui-state-focus" );
})
.bind( "blur.accordion", function() {
if ( options.disabled ) {
return;
}
$( this ).removeClass( "ui-state-focus" );
});
self.headers.next()
.addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" );
if ( options.navigation ) {
var current = self.element.find( "a" ).filter( options.navigationFilter ).eq( 0 );
if ( current.length ) {
var header = current.closest( ".ui-accordion-header" );
if ( header.length ) {
// anchor within header
self.active = header;
} else {
// anchor within content
self.active = current.closest( ".ui-accordion-content" ).prev();
}
}
}
self.active = self._findActive( self.active || options.active )
.addClass( "ui-state-default ui-state-active" )
.toggleClass( "ui-corner-all" )
.toggleClass( "ui-corner-top" );
self.active.next().addClass( "ui-accordion-content-active" );
self._createIcons();
self.resize();
// ARIA
self.element.attr( "role", "tablist" );
self.headers
.attr( "role", "tab" )
.bind( "keydown.accordion", function( event ) {
return self._keydown( event );
})
.next()
.attr( "role", "tabpanel" );
self.headers
.not( self.active || "" )
.attr({
"aria-expanded": "false",
"aria-selected": "false",
tabIndex: -1
})
.next()
.hide();
// make sure at least one header is in the tab order
if ( !self.active.length ) {
self.headers.eq( 0 ).attr( "tabIndex", 0 );
} else {
self.active
.attr({
"aria-expanded": "true",
"aria-selected": "true",
tabIndex: 0
});
}
// only need links in tab order for Safari
if ( !$.browser.safari ) {
self.headers.find( "a" ).attr( "tabIndex", -1 );
}
if ( options.event ) {
self.headers.bind( options.event.split(" ").join(".accordion ") + ".accordion", function(event) {
self._clickHandler.call( self, event, this );
event.preventDefault();
});
}
},
_createIcons: function() {
var options = this.options;
if ( options.icons ) {
$( "<span></span>" )
.addClass( "ui-icon " + options.icons.header )
.prependTo( this.headers );
this.active.children( ".ui-icon" )
.toggleClass(options.icons.header)
.toggleClass(options.icons.headerSelected);
this.element.addClass( "ui-accordion-icons" );
}
},
_destroyIcons: function() {
this.headers.children( ".ui-icon" ).remove();
this.element.removeClass( "ui-accordion-icons" );
},
destroy: function() {
var options = this.options;
this.element
.removeClass( "ui-accordion ui-widget ui-helper-reset" )
.removeAttr( "role" );
this.headers
.unbind( ".accordion" )
.removeClass( "ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top" )
.removeAttr( "role" )
.removeAttr( "aria-expanded" )
.removeAttr( "aria-selected" )
.removeAttr( "tabIndex" );
this.headers.find( "a" ).removeAttr( "tabIndex" );
this._destroyIcons();
var contents = this.headers.next()
.css( "display", "" )
.removeAttr( "role" )
.removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled" );
if ( options.autoHeight || options.fillHeight ) {
contents.css( "height", "" );
}
return $.Widget.prototype.destroy.call( this );
},
_setOption: function( key, value ) {
$.Widget.prototype._setOption.apply( this, arguments );
if ( key == "active" ) {
this.activate( value );
}
if ( key == "icons" ) {
this._destroyIcons();
if ( value ) {
this._createIcons();
}
}
// #5332 - opacity doesn't cascade to positioned elements in IE
// so we need to add the disabled class to the headers and panels
if ( key == "disabled" ) {
this.headers.add(this.headers.next())
[ value ? "addClass" : "removeClass" ](
"ui-accordion-disabled ui-state-disabled" );
}
},
_keydown: function( event ) {
if ( this.options.disabled || event.altKey || event.ctrlKey ) {
return;
}
var keyCode = $.ui.keyCode,
length = this.headers.length,
currentIndex = this.headers.index( event.target ),
toFocus = false;
switch ( event.keyCode ) {
case keyCode.RIGHT:
case keyCode.DOWN:
toFocus = this.headers[ ( currentIndex + 1 ) % length ];
break;
case keyCode.LEFT:
case keyCode.UP:
toFocus = this.headers[ ( currentIndex - 1 + length ) % length ];
break;
case keyCode.SPACE:
case keyCode.ENTER:
this._clickHandler( { target: event.target }, event.target );
event.preventDefault();
}
if ( toFocus ) {
$( event.target ).attr( "tabIndex", -1 );
$( toFocus ).attr( "tabIndex", 0 );
toFocus.focus();
return false;
}
return true;
},
resize: function() {
var options = this.options,
maxHeight;
if ( options.fillSpace ) {
if ( $.browser.msie ) {
var defOverflow = this.element.parent().css( "overflow" );
this.element.parent().css( "overflow", "hidden");
}
maxHeight = this.element.parent().height();
if ($.browser.msie) {
this.element.parent().css( "overflow", defOverflow );
}
this.headers.each(function() {
maxHeight -= $( this ).outerHeight( true );
});
this.headers.next()
.each(function() {
$( this ).height( Math.max( 0, maxHeight -
$( this ).innerHeight() + $( this ).height() ) );
})
.css( "overflow", "auto" );
} else if ( options.autoHeight ) {
maxHeight = 0;
this.headers.next()
.each(function() {
maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() );
})
.height( maxHeight );
}
return this;
},
activate: function( index ) {
// TODO this gets called on init, changing the option without an explicit call for that
this.options.active = index;
// call clickHandler with custom event
var active = this._findActive( index )[ 0 ];
this._clickHandler( { target: active }, active );
return this;
},
_findActive: function( selector ) {
return selector
? typeof selector === "number"
? this.headers.filter( ":eq(" + selector + ")" )
: this.headers.not( this.headers.not( selector ) )
: selector === false
? $( [] )
: this.headers.filter( ":eq(0)" );
},
// TODO isn't event.target enough? why the separate target argument?
_clickHandler: function( event, target ) {
var options = this.options;
if ( options.disabled ) {
return;
}
// called only when using activate(false) to close all parts programmatically
if ( !event.target ) {
if ( !options.collapsible ) {
return;
}
this.active
.removeClass( "ui-state-active ui-corner-top" )
.addClass( "ui-state-default ui-corner-all" )
.children( ".ui-icon" )
.removeClass( options.icons.headerSelected )
.addClass( options.icons.header );
this.active.next().addClass( "ui-accordion-content-active" );
var toHide = this.active.next(),
data = {
options: options,
newHeader: $( [] ),
oldHeader: options.active,
newContent: $( [] ),
oldContent: toHide
},
toShow = ( this.active = $( [] ) );
this._toggle( toShow, toHide, data );
return;
}
// get the click target
var clicked = $( event.currentTarget || target ),
clickedIsActive = clicked[0] === this.active[0];
// TODO the option is changed, is that correct?
// TODO if it is correct, shouldn't that happen after determining that the click is valid?
options.active = options.collapsible && clickedIsActive ?
false :
this.headers.index( clicked );
// if animations are still active, or the active header is the target, ignore click
if ( this.running || ( !options.collapsible && clickedIsActive ) ) {
return;
}
// find elements to show and hide
var active = this.active,
toShow = clicked.next(),
toHide = this.active.next(),
data = {
options: options,
newHeader: clickedIsActive && options.collapsible ? $([]) : clicked,
oldHeader: this.active,
newContent: clickedIsActive && options.collapsible ? $([]) : toShow,
oldContent: toHide
},
down = this.headers.index( this.active[0] ) > this.headers.index( clicked[0] );
// when the call to ._toggle() comes after the class changes
// it causes a very odd bug in IE 8 (see #6720)
this.active = clickedIsActive ? $([]) : clicked;
this._toggle( toShow, toHide, data, clickedIsActive, down );
// switch classes
active
.removeClass( "ui-state-active ui-corner-top" )
.addClass( "ui-state-default ui-corner-all" )
.children( ".ui-icon" )
.removeClass( options.icons.headerSelected )
.addClass( options.icons.header );
if ( !clickedIsActive ) {
clicked
.removeClass( "ui-state-default ui-corner-all" )
.addClass( "ui-state-active ui-corner-top" )
.children( ".ui-icon" )
.removeClass( options.icons.header )
.addClass( options.icons.headerSelected );
clicked
.next()
.addClass( "ui-accordion-content-active" );
}
return;
},
_toggle: function( toShow, toHide, data, clickedIsActive, down ) {
var self = this,
options = self.options;
self.toShow = toShow;
self.toHide = toHide;
self.data = data;
var complete = function() {
if ( !self ) {
return;
}
return self._completed.apply( self, arguments );
};
// trigger changestart event
self._trigger( "changestart", null, self.data );
// count elements to animate
self.running = toHide.size() === 0 ? toShow.size() : toHide.size();
if ( options.animated ) {
var animOptions = {};
if ( options.collapsible && clickedIsActive ) {
animOptions = {
toShow: $( [] ),
toHide: toHide,
complete: complete,
down: down,
autoHeight: options.autoHeight || options.fillSpace
};
} else {
animOptions = {
toShow: toShow,
toHide: toHide,
complete: complete,
down: down,
autoHeight: options.autoHeight || options.fillSpace
};
}
if ( !options.proxied ) {
options.proxied = options.animated;
}
if ( !options.proxiedDuration ) {
options.proxiedDuration = options.duration;
}
options.animated = $.isFunction( options.proxied ) ?
options.proxied( animOptions ) :
options.proxied;
options.duration = $.isFunction( options.proxiedDuration ) ?
options.proxiedDuration( animOptions ) :
options.proxiedDuration;
var animations = $.ui.accordion.animations,
duration = options.duration,
easing = options.animated;
if ( easing && !animations[ easing ] && !$.easing[ easing ] ) {
easing = "slide";
}
if ( !animations[ easing ] ) {
animations[ easing ] = function( options ) {
this.slide( options, {
easing: easing,
duration: duration || 700
});
};
}
animations[ easing ]( animOptions );
} else {
if ( options.collapsible && clickedIsActive ) {
toShow.toggle();
} else {
toHide.hide();
toShow.show();
}
complete( true );
}
// TODO assert that the blur and focus triggers are really necessary, remove otherwise
toHide.prev()
.attr({
"aria-expanded": "false",
"aria-selected": "false",
tabIndex: -1
})
.blur();
toShow.prev()
.attr({
"aria-expanded": "true",
"aria-selected": "true",
tabIndex: 0
})
.focus();
},
_completed: function( cancel ) {
this.running = cancel ? 0 : --this.running;
if ( this.running ) {
return;
}
if ( this.options.clearStyle ) {
this.toShow.add( this.toHide ).css({
height: "",
overflow: ""
});
}
// other classes are removed before the animation; this one needs to stay until completed
this.toHide.removeClass( "ui-accordion-content-active" );
// Work around for rendering bug in IE (#5421)
if ( this.toHide.length ) {
this.toHide.parent()[0].className = this.toHide.parent()[0].className;
}
this._trigger( "change", null, this.data );
}
});
$.extend( $.ui.accordion, {
version: "1.8.17",
animations: {
slide: function( options, additions ) {
options = $.extend({
easing: "swing",
duration: 300
}, options, additions );
if ( !options.toHide.size() ) {
options.toShow.animate({
height: "show",
paddingTop: "show",
paddingBottom: "show"
}, options );
return;
}
if ( !options.toShow.size() ) {
options.toHide.animate({
height: "hide",
paddingTop: "hide",
paddingBottom: "hide"
}, options );
return;
}
var overflow = options.toShow.css( "overflow" ),
percentDone = 0,
showProps = {},
hideProps = {},
fxAttrs = [ "height", "paddingTop", "paddingBottom" ],
originalWidth;
// fix width before calculating height of hidden element
var s = options.toShow;
originalWidth = s[0].style.width;
s.width( s.parent().width()
- parseFloat( s.css( "paddingLeft" ) )
- parseFloat( s.css( "paddingRight" ) )
- ( parseFloat( s.css( "borderLeftWidth" ) ) || 0 )
- ( parseFloat( s.css( "borderRightWidth" ) ) || 0 ) );
$.each( fxAttrs, function( i, prop ) {
hideProps[ prop ] = "hide";
var parts = ( "" + $.css( options.toShow[0], prop ) ).match( /^([\d+-.]+)(.*)$/ );
showProps[ prop ] = {
value: parts[ 1 ],
unit: parts[ 2 ] || "px"
};
});
options.toShow.css({ height: 0, overflow: "hidden" }).show();
options.toHide
.filter( ":hidden" )
.each( options.complete )
.end()
.filter( ":visible" )
.animate( hideProps, {
step: function( now, settings ) {
// only calculate the percent when animating height
// IE gets very inconsistent results when animating elements
// with small values, which is common for padding
if ( settings.prop == "height" ) {
percentDone = ( settings.end - settings.start === 0 ) ? 0 :
( settings.now - settings.start ) / ( settings.end - settings.start );
}
options.toShow[ 0 ].style[ settings.prop ] =
( percentDone * showProps[ settings.prop ].value )
+ showProps[ settings.prop ].unit;
},
duration: options.duration,
easing: options.easing,
complete: function() {
if ( !options.autoHeight ) {
options.toShow.css( "height", "" );
}
options.toShow.css({
width: originalWidth,
overflow: overflow
});
options.complete();
}
});
},
bounceslide: function( options ) {
this.slide( options, {
easing: options.down ? "easeOutBounce" : "swing",
duration: options.down ? 1000 : 200
});
}
}
});
})( jQuery );

View file

@ -0,0 +1,622 @@
/*
* jQuery UI Autocomplete 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Autocomplete
*
* Depends:
* jquery.ui.core.js
* jquery.ui.widget.js
* jquery.ui.position.js
*/
(function( $, undefined ) {
// used to prevent race conditions with remote data sources
var requestIndex = 0;
$.widget( "ui.autocomplete", {
options: {
appendTo: "body",
autoFocus: false,
delay: 300,
minLength: 1,
position: {
my: "left top",
at: "left bottom",
collision: "none"
},
source: null
},
pending: 0,
_create: function() {
var self = this,
doc = this.element[ 0 ].ownerDocument,
suppressKeyPress;
this.element
.addClass( "ui-autocomplete-input" )
.attr( "autocomplete", "off" )
// TODO verify these actually work as intended
.attr({
role: "textbox",
"aria-autocomplete": "list",
"aria-haspopup": "true"
})
.bind( "keydown.autocomplete", function( event ) {
if ( self.options.disabled || self.element.propAttr( "readOnly" ) ) {
return;
}
suppressKeyPress = false;
var keyCode = $.ui.keyCode;
switch( event.keyCode ) {
case keyCode.PAGE_UP:
self._move( "previousPage", event );
break;
case keyCode.PAGE_DOWN:
self._move( "nextPage", event );
break;
case keyCode.UP:
self._move( "previous", event );
// prevent moving cursor to beginning of text field in some browsers
event.preventDefault();
break;
case keyCode.DOWN:
self._move( "next", event );
// prevent moving cursor to end of text field in some browsers
event.preventDefault();
break;
case keyCode.ENTER:
case keyCode.NUMPAD_ENTER:
// when menu is open and has focus
if ( self.menu.active ) {
// #6055 - Opera still allows the keypress to occur
// which causes forms to submit
suppressKeyPress = true;
event.preventDefault();
}
//passthrough - ENTER and TAB both select the current element
case keyCode.TAB:
if ( !self.menu.active ) {
return;
}
self.menu.select( event );
break;
case keyCode.ESCAPE:
self.element.val( self.term );
self.close( event );
break;
default:
// keypress is triggered before the input value is changed
clearTimeout( self.searching );
self.searching = setTimeout(function() {
// only search if the value has changed
if ( self.term != self.element.val() ) {
self.selectedItem = null;
self.search( null, event );
}
}, self.options.delay );
break;
}
})
.bind( "keypress.autocomplete", function( event ) {
if ( suppressKeyPress ) {
suppressKeyPress = false;
event.preventDefault();
}
})
.bind( "focus.autocomplete", function() {
if ( self.options.disabled ) {
return;
}
self.selectedItem = null;
self.previous = self.element.val();
})
.bind( "blur.autocomplete", function( event ) {
if ( self.options.disabled ) {
return;
}
clearTimeout( self.searching );
// clicks on the menu (or a button to trigger a search) will cause a blur event
self.closing = setTimeout(function() {
self.close( event );
self._change( event );
}, 150 );
});
this._initSource();
this.response = function() {
return self._response.apply( self, arguments );
};
this.menu = $( "<ul></ul>" )
.addClass( "ui-autocomplete" )
.appendTo( $( this.options.appendTo || "body", doc )[0] )
// prevent the close-on-blur in case of a "slow" click on the menu (long mousedown)
.mousedown(function( event ) {
// clicking on the scrollbar causes focus to shift to the body
// but we can't detect a mouseup or a click immediately afterward
// so we have to track the next mousedown and close the menu if
// the user clicks somewhere outside of the autocomplete
var menuElement = self.menu.element[ 0 ];
if ( !$( event.target ).closest( ".ui-menu-item" ).length ) {
setTimeout(function() {
$( document ).one( 'mousedown', function( event ) {
if ( event.target !== self.element[ 0 ] &&
event.target !== menuElement &&
!$.ui.contains( menuElement, event.target ) ) {
self.close();
}
});
}, 1 );
}
// use another timeout to make sure the blur-event-handler on the input was already triggered
setTimeout(function() {
clearTimeout( self.closing );
}, 13);
})
.menu({
focus: function( event, ui ) {
var item = ui.item.data( "item.autocomplete" );
if ( false !== self._trigger( "focus", event, { item: item } ) ) {
// use value to match what will end up in the input, if it was a key event
if ( /^key/.test(event.originalEvent.type) ) {
self.element.val( item.value );
}
}
},
selected: function( event, ui ) {
var item = ui.item.data( "item.autocomplete" ),
previous = self.previous;
// only trigger when focus was lost (click on menu)
if ( self.element[0] !== doc.activeElement ) {
self.element.focus();
self.previous = previous;
// #6109 - IE triggers two focus events and the second
// is asynchronous, so we need to reset the previous
// term synchronously and asynchronously :-(
setTimeout(function() {
self.previous = previous;
self.selectedItem = item;
}, 1);
}
if ( false !== self._trigger( "select", event, { item: item } ) ) {
self.element.val( item.value );
}
// reset the term after the select event
// this allows custom select handling to work properly
self.term = self.element.val();
self.close( event );
self.selectedItem = item;
},
blur: function( event, ui ) {
// don't set the value of the text field if it's already correct
// this prevents moving the cursor unnecessarily
if ( self.menu.element.is(":visible") &&
( self.element.val() !== self.term ) ) {
self.element.val( self.term );
}
}
})
.zIndex( this.element.zIndex() + 1 )
// workaround for jQuery bug #5781 http://dev.jquery.com/ticket/5781
.css({ top: 0, left: 0 })
.hide()
.data( "menu" );
if ( $.fn.bgiframe ) {
this.menu.element.bgiframe();
}
// turning off autocomplete prevents the browser from remembering the
// value when navigating through history, so we re-enable autocomplete
// if the page is unloaded before the widget is destroyed. #7790
self.beforeunloadHandler = function() {
self.element.removeAttr( "autocomplete" );
};
$( window ).bind( "beforeunload", self.beforeunloadHandler );
},
destroy: function() {
this.element
.removeClass( "ui-autocomplete-input" )
.removeAttr( "autocomplete" )
.removeAttr( "role" )
.removeAttr( "aria-autocomplete" )
.removeAttr( "aria-haspopup" );
this.menu.element.remove();
$( window ).unbind( "beforeunload", this.beforeunloadHandler );
$.Widget.prototype.destroy.call( this );
},
_setOption: function( key, value ) {
$.Widget.prototype._setOption.apply( this, arguments );
if ( key === "source" ) {
this._initSource();
}
if ( key === "appendTo" ) {
this.menu.element.appendTo( $( value || "body", this.element[0].ownerDocument )[0] )
}
if ( key === "disabled" && value && this.xhr ) {
this.xhr.abort();
}
},
_initSource: function() {
var self = this,
array,
url;
if ( $.isArray(this.options.source) ) {
array = this.options.source;
this.source = function( request, response ) {
response( $.ui.autocomplete.filter(array, request.term) );
};
} else if ( typeof this.options.source === "string" ) {
url = this.options.source;
this.source = function( request, response ) {
if ( self.xhr ) {
self.xhr.abort();
}
self.xhr = $.ajax({
url: url,
data: request,
dataType: "json",
autocompleteRequest: ++requestIndex,
success: function( data, status ) {
if ( this.autocompleteRequest === requestIndex ) {
response( data );
}
},
error: function() {
if ( this.autocompleteRequest === requestIndex ) {
response( [] );
}
}
});
};
} else {
this.source = this.options.source;
}
},
search: function( value, event ) {
value = value != null ? value : this.element.val();
// always save the actual value, not the one passed as an argument
this.term = this.element.val();
if ( value.length < this.options.minLength ) {
return this.close( event );
}
clearTimeout( this.closing );
if ( this._trigger( "search", event ) === false ) {
return;
}
return this._search( value );
},
_search: function( value ) {
this.pending++;
this.element.addClass( "ui-autocomplete-loading" );
this.source( { term: value }, this.response );
},
_response: function( content ) {
if ( !this.options.disabled && content && content.length ) {
content = this._normalize( content );
this._suggest( content );
this._trigger( "open" );
} else {
this.close();
}
this.pending--;
if ( !this.pending ) {
this.element.removeClass( "ui-autocomplete-loading" );
}
},
close: function( event ) {
clearTimeout( this.closing );
if ( this.menu.element.is(":visible") ) {
this.menu.element.hide();
this.menu.deactivate();
this._trigger( "close", event );
}
},
_change: function( event ) {
if ( this.previous !== this.element.val() ) {
this._trigger( "change", event, { item: this.selectedItem } );
}
},
_normalize: function( items ) {
// assume all items have the right format when the first item is complete
if ( items.length && items[0].label && items[0].value ) {
return items;
}
return $.map( items, function(item) {
if ( typeof item === "string" ) {
return {
label: item,
value: item
};
}
return $.extend({
label: item.label || item.value,
value: item.value || item.label
}, item );
});
},
_suggest: function( items ) {
var ul = this.menu.element
.empty()
.zIndex( this.element.zIndex() + 1 );
this._renderMenu( ul, items );
// TODO refresh should check if the active item is still in the dom, removing the need for a manual deactivate
this.menu.deactivate();
this.menu.refresh();
// size and position menu
ul.show();
this._resizeMenu();
ul.position( $.extend({
of: this.element
}, this.options.position ));
if ( this.options.autoFocus ) {
this.menu.next( new $.Event("mouseover") );
}
},
_resizeMenu: function() {
var ul = this.menu.element;
ul.outerWidth( Math.max(
// Firefox wraps long text (possibly a rounding bug)
// so we add 1px to avoid the wrapping (#7513)
ul.width( "" ).outerWidth() + 1,
this.element.outerWidth()
) );
},
_renderMenu: function( ul, items ) {
var self = this;
$.each( items, function( index, item ) {
self._renderItem( ul, item );
});
},
_renderItem: function( ul, item) {
return $( "<li></li>" )
.data( "item.autocomplete", item )
.append( $( "<a></a>" ).text( item.label ) )
.appendTo( ul );
},
_move: function( direction, event ) {
if ( !this.menu.element.is(":visible") ) {
this.search( null, event );
return;
}
if ( this.menu.first() && /^previous/.test(direction) ||
this.menu.last() && /^next/.test(direction) ) {
this.element.val( this.term );
this.menu.deactivate();
return;
}
this.menu[ direction ]( event );
},
widget: function() {
return this.menu.element;
}
});
$.extend( $.ui.autocomplete, {
escapeRegex: function( value ) {
return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
},
filter: function(array, term) {
var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
return $.grep( array, function(value) {
return matcher.test( value.label || value.value || value );
});
}
});
}( jQuery ));
/*
* jQuery UI Menu (not officially released)
*
* This widget isn't yet finished and the API is subject to change. We plan to finish
* it for the next release. You're welcome to give it a try anyway and give us feedback,
* as long as you're okay with migrating your code later on. We can help with that, too.
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Menu
*
* Depends:
* jquery.ui.core.js
* jquery.ui.widget.js
*/
(function($) {
$.widget("ui.menu", {
_create: function() {
var self = this;
this.element
.addClass("ui-menu ui-widget ui-widget-content ui-corner-all")
.attr({
role: "listbox",
"aria-activedescendant": "ui-active-menuitem"
})
.click(function( event ) {
if ( !$( event.target ).closest( ".ui-menu-item a" ).length ) {
return;
}
// temporary
event.preventDefault();
self.select( event );
});
this.refresh();
},
refresh: function() {
var self = this;
// don't refresh list items that are already adapted
var items = this.element.children("li:not(.ui-menu-item):has(a)")
.addClass("ui-menu-item")
.attr("role", "menuitem");
items.children("a")
.addClass("ui-corner-all")
.attr("tabindex", -1)
// mouseenter doesn't work with event delegation
.mouseenter(function( event ) {
self.activate( event, $(this).parent() );
})
.mouseleave(function() {
self.deactivate();
});
},
activate: function( event, item ) {
this.deactivate();
if (this.hasScroll()) {
var offset = item.offset().top - this.element.offset().top,
scroll = this.element.scrollTop(),
elementHeight = this.element.height();
if (offset < 0) {
this.element.scrollTop( scroll + offset);
} else if (offset >= elementHeight) {
this.element.scrollTop( scroll + offset - elementHeight + item.height());
}
}
this.active = item.eq(0)
.children("a")
.addClass("ui-state-hover")
.attr("id", "ui-active-menuitem")
.end();
this._trigger("focus", event, { item: item });
},
deactivate: function() {
if (!this.active) { return; }
this.active.children("a")
.removeClass("ui-state-hover")
.removeAttr("id");
this._trigger("blur");
this.active = null;
},
next: function(event) {
this.move("next", ".ui-menu-item:first", event);
},
previous: function(event) {
this.move("prev", ".ui-menu-item:last", event);
},
first: function() {
return this.active && !this.active.prevAll(".ui-menu-item").length;
},
last: function() {
return this.active && !this.active.nextAll(".ui-menu-item").length;
},
move: function(direction, edge, event) {
if (!this.active) {
this.activate(event, this.element.children(edge));
return;
}
var next = this.active[direction + "All"](".ui-menu-item").eq(0);
if (next.length) {
this.activate(event, next);
} else {
this.activate(event, this.element.children(edge));
}
},
// TODO merge with previousPage
nextPage: function(event) {
if (this.hasScroll()) {
// TODO merge with no-scroll-else
if (!this.active || this.last()) {
this.activate(event, this.element.children(".ui-menu-item:first"));
return;
}
var base = this.active.offset().top,
height = this.element.height(),
result = this.element.children(".ui-menu-item").filter(function() {
var close = $(this).offset().top - base - height + $(this).height();
// TODO improve approximation
return close < 10 && close > -10;
});
// TODO try to catch this earlier when scrollTop indicates the last page anyway
if (!result.length) {
result = this.element.children(".ui-menu-item:last");
}
this.activate(event, result);
} else {
this.activate(event, this.element.children(".ui-menu-item")
.filter(!this.active || this.last() ? ":first" : ":last"));
}
},
// TODO merge with nextPage
previousPage: function(event) {
if (this.hasScroll()) {
// TODO merge with no-scroll-else
if (!this.active || this.first()) {
this.activate(event, this.element.children(".ui-menu-item:last"));
return;
}
var base = this.active.offset().top,
height = this.element.height();
result = this.element.children(".ui-menu-item").filter(function() {
var close = $(this).offset().top - base + height - $(this).height();
// TODO improve approximation
return close < 10 && close > -10;
});
// TODO try to catch this earlier when scrollTop indicates the last page anyway
if (!result.length) {
result = this.element.children(".ui-menu-item:first");
}
this.activate(event, result);
} else {
this.activate(event, this.element.children(".ui-menu-item")
.filter(!this.active || this.first() ? ":last" : ":first"));
}
},
hasScroll: function() {
return this.element.height() < this.element[ $.fn.prop ? "prop" : "attr" ]("scrollHeight");
},
select: function( event ) {
this._trigger("selected", event, { item: this.active });
}
});
}(jQuery));

View file

@ -0,0 +1,416 @@
/*
* jQuery UI Button 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Button
*
* Depends:
* jquery.ui.core.js
* jquery.ui.widget.js
*/
(function( $, undefined ) {
var lastActive, startXPos, startYPos, clickDragged,
baseClasses = "ui-button ui-widget ui-state-default ui-corner-all",
stateClasses = "ui-state-hover ui-state-active ",
typeClasses = "ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only",
formResetHandler = function() {
var buttons = $( this ).find( ":ui-button" );
setTimeout(function() {
buttons.button( "refresh" );
}, 1 );
},
radioGroup = function( radio ) {
var name = radio.name,
form = radio.form,
radios = $( [] );
if ( name ) {
if ( form ) {
radios = $( form ).find( "[name='" + name + "']" );
} else {
radios = $( "[name='" + name + "']", radio.ownerDocument )
.filter(function() {
return !this.form;
});
}
}
return radios;
};
$.widget( "ui.button", {
options: {
disabled: null,
text: true,
label: null,
icons: {
primary: null,
secondary: null
}
},
_create: function() {
this.element.closest( "form" )
.unbind( "reset.button" )
.bind( "reset.button", formResetHandler );
if ( typeof this.options.disabled !== "boolean" ) {
this.options.disabled = this.element.propAttr( "disabled" );
}
this._determineButtonType();
this.hasTitle = !!this.buttonElement.attr( "title" );
var self = this,
options = this.options,
toggleButton = this.type === "checkbox" || this.type === "radio",
hoverClass = "ui-state-hover" + ( !toggleButton ? " ui-state-active" : "" ),
focusClass = "ui-state-focus";
if ( options.label === null ) {
options.label = this.buttonElement.html();
}
if ( this.element.is( ":disabled" ) ) {
options.disabled = true;
}
this.buttonElement
.addClass( baseClasses )
.attr( "role", "button" )
.bind( "mouseenter.button", function() {
if ( options.disabled ) {
return;
}
$( this ).addClass( "ui-state-hover" );
if ( this === lastActive ) {
$( this ).addClass( "ui-state-active" );
}
})
.bind( "mouseleave.button", function() {
if ( options.disabled ) {
return;
}
$( this ).removeClass( hoverClass );
})
.bind( "click.button", function( event ) {
if ( options.disabled ) {
event.preventDefault();
event.stopImmediatePropagation();
}
});
this.element
.bind( "focus.button", function() {
// no need to check disabled, focus won't be triggered anyway
self.buttonElement.addClass( focusClass );
})
.bind( "blur.button", function() {
self.buttonElement.removeClass( focusClass );
});
if ( toggleButton ) {
this.element.bind( "change.button", function() {
if ( clickDragged ) {
return;
}
self.refresh();
});
// if mouse moves between mousedown and mouseup (drag) set clickDragged flag
// prevents issue where button state changes but checkbox/radio checked state
// does not in Firefox (see ticket #6970)
this.buttonElement
.bind( "mousedown.button", function( event ) {
if ( options.disabled ) {
return;
}
clickDragged = false;
startXPos = event.pageX;
startYPos = event.pageY;
})
.bind( "mouseup.button", function( event ) {
if ( options.disabled ) {
return;
}
if ( startXPos !== event.pageX || startYPos !== event.pageY ) {
clickDragged = true;
}
});
}
if ( this.type === "checkbox" ) {
this.buttonElement.bind( "click.button", function() {
if ( options.disabled || clickDragged ) {
return false;
}
$( this ).toggleClass( "ui-state-active" );
self.buttonElement.attr( "aria-pressed", self.element[0].checked );
});
} else if ( this.type === "radio" ) {
this.buttonElement.bind( "click.button", function() {
if ( options.disabled || clickDragged ) {
return false;
}
$( this ).addClass( "ui-state-active" );
self.buttonElement.attr( "aria-pressed", "true" );
var radio = self.element[ 0 ];
radioGroup( radio )
.not( radio )
.map(function() {
return $( this ).button( "widget" )[ 0 ];
})
.removeClass( "ui-state-active" )
.attr( "aria-pressed", "false" );
});
} else {
this.buttonElement
.bind( "mousedown.button", function() {
if ( options.disabled ) {
return false;
}
$( this ).addClass( "ui-state-active" );
lastActive = this;
$( document ).one( "mouseup", function() {
lastActive = null;
});
})
.bind( "mouseup.button", function() {
if ( options.disabled ) {
return false;
}
$( this ).removeClass( "ui-state-active" );
})
.bind( "keydown.button", function(event) {
if ( options.disabled ) {
return false;
}
if ( event.keyCode == $.ui.keyCode.SPACE || event.keyCode == $.ui.keyCode.ENTER ) {
$( this ).addClass( "ui-state-active" );
}
})
.bind( "keyup.button", function() {
$( this ).removeClass( "ui-state-active" );
});
if ( this.buttonElement.is("a") ) {
this.buttonElement.keyup(function(event) {
if ( event.keyCode === $.ui.keyCode.SPACE ) {
// TODO pass through original event correctly (just as 2nd argument doesn't work)
$( this ).click();
}
});
}
}
// TODO: pull out $.Widget's handling for the disabled option into
// $.Widget.prototype._setOptionDisabled so it's easy to proxy and can
// be overridden by individual plugins
this._setOption( "disabled", options.disabled );
this._resetButton();
},
_determineButtonType: function() {
if ( this.element.is(":checkbox") ) {
this.type = "checkbox";
} else if ( this.element.is(":radio") ) {
this.type = "radio";
} else if ( this.element.is("input") ) {
this.type = "input";
} else {
this.type = "button";
}
if ( this.type === "checkbox" || this.type === "radio" ) {
// we don't search against the document in case the element
// is disconnected from the DOM
var ancestor = this.element.parents().filter(":last"),
labelSelector = "label[for='" + this.element.attr("id") + "']";
this.buttonElement = ancestor.find( labelSelector );
if ( !this.buttonElement.length ) {
ancestor = ancestor.length ? ancestor.siblings() : this.element.siblings();
this.buttonElement = ancestor.filter( labelSelector );
if ( !this.buttonElement.length ) {
this.buttonElement = ancestor.find( labelSelector );
}
}
this.element.addClass( "ui-helper-hidden-accessible" );
var checked = this.element.is( ":checked" );
if ( checked ) {
this.buttonElement.addClass( "ui-state-active" );
}
this.buttonElement.attr( "aria-pressed", checked );
} else {
this.buttonElement = this.element;
}
},
widget: function() {
return this.buttonElement;
},
destroy: function() {
this.element
.removeClass( "ui-helper-hidden-accessible" );
this.buttonElement
.removeClass( baseClasses + " " + stateClasses + " " + typeClasses )
.removeAttr( "role" )
.removeAttr( "aria-pressed" )
.html( this.buttonElement.find(".ui-button-text").html() );
if ( !this.hasTitle ) {
this.buttonElement.removeAttr( "title" );
}
$.Widget.prototype.destroy.call( this );
},
_setOption: function( key, value ) {
$.Widget.prototype._setOption.apply( this, arguments );
if ( key === "disabled" ) {
if ( value ) {
this.element.propAttr( "disabled", true );
} else {
this.element.propAttr( "disabled", false );
}
return;
}
this._resetButton();
},
refresh: function() {
var isDisabled = this.element.is( ":disabled" );
if ( isDisabled !== this.options.disabled ) {
this._setOption( "disabled", isDisabled );
}
if ( this.type === "radio" ) {
radioGroup( this.element[0] ).each(function() {
if ( $( this ).is( ":checked" ) ) {
$( this ).button( "widget" )
.addClass( "ui-state-active" )
.attr( "aria-pressed", "true" );
} else {
$( this ).button( "widget" )
.removeClass( "ui-state-active" )
.attr( "aria-pressed", "false" );
}
});
} else if ( this.type === "checkbox" ) {
if ( this.element.is( ":checked" ) ) {
this.buttonElement
.addClass( "ui-state-active" )
.attr( "aria-pressed", "true" );
} else {
this.buttonElement
.removeClass( "ui-state-active" )
.attr( "aria-pressed", "false" );
}
}
},
_resetButton: function() {
if ( this.type === "input" ) {
if ( this.options.label ) {
this.element.val( this.options.label );
}
return;
}
var buttonElement = this.buttonElement.removeClass( typeClasses ),
buttonText = $( "<span></span>", this.element[0].ownerDocument )
.addClass( "ui-button-text" )
.html( this.options.label )
.appendTo( buttonElement.empty() )
.text(),
icons = this.options.icons,
multipleIcons = icons.primary && icons.secondary,
buttonClasses = [];
if ( icons.primary || icons.secondary ) {
if ( this.options.text ) {
buttonClasses.push( "ui-button-text-icon" + ( multipleIcons ? "s" : ( icons.primary ? "-primary" : "-secondary" ) ) );
}
if ( icons.primary ) {
buttonElement.prepend( "<span class='ui-button-icon-primary ui-icon " + icons.primary + "'></span>" );
}
if ( icons.secondary ) {
buttonElement.append( "<span class='ui-button-icon-secondary ui-icon " + icons.secondary + "'></span>" );
}
if ( !this.options.text ) {
buttonClasses.push( multipleIcons ? "ui-button-icons-only" : "ui-button-icon-only" );
if ( !this.hasTitle ) {
buttonElement.attr( "title", buttonText );
}
}
} else {
buttonClasses.push( "ui-button-text-only" );
}
buttonElement.addClass( buttonClasses.join( " " ) );
}
});
$.widget( "ui.buttonset", {
options: {
items: ":button, :submit, :reset, :checkbox, :radio, a, :data(button)"
},
_create: function() {
this.element.addClass( "ui-buttonset" );
},
_init: function() {
this.refresh();
},
_setOption: function( key, value ) {
if ( key === "disabled" ) {
this.buttons.button( "option", key, value );
}
$.Widget.prototype._setOption.apply( this, arguments );
},
refresh: function() {
var rtl = this.element.css( "direction" ) === "rtl";
this.buttons = this.element.find( this.options.items )
.filter( ":ui-button" )
.button( "refresh" )
.end()
.not( ":ui-button" )
.button()
.end()
.map(function() {
return $( this ).button( "widget" )[ 0 ];
})
.removeClass( "ui-corner-all ui-corner-left ui-corner-right" )
.filter( ":first" )
.addClass( rtl ? "ui-corner-right" : "ui-corner-left" )
.end()
.filter( ":last" )
.addClass( rtl ? "ui-corner-left" : "ui-corner-right" )
.end()
.end();
},
destroy: function() {
this.element.removeClass( "ui-buttonset" );
this.buttons
.map(function() {
return $( this ).button( "widget" )[ 0 ];
})
.removeClass( "ui-corner-left ui-corner-right" )
.end()
.button( "destroy" );
$.Widget.prototype.destroy.call( this );
}
});
}( jQuery ) );

View file

@ -0,0 +1,314 @@
/*!
* jQuery UI 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI
*/
(function( $, undefined ) {
// prevent duplicate loading
// this is only a problem because we proxy existing functions
// and we don't want to double proxy them
$.ui = $.ui || {};
if ( $.ui.version ) {
return;
}
$.extend( $.ui, {
version: "1.8.17",
keyCode: {
ALT: 18,
BACKSPACE: 8,
CAPS_LOCK: 20,
COMMA: 188,
COMMAND: 91,
COMMAND_LEFT: 91, // COMMAND
COMMAND_RIGHT: 93,
CONTROL: 17,
DELETE: 46,
DOWN: 40,
END: 35,
ENTER: 13,
ESCAPE: 27,
HOME: 36,
INSERT: 45,
LEFT: 37,
MENU: 93, // COMMAND_RIGHT
NUMPAD_ADD: 107,
NUMPAD_DECIMAL: 110,
NUMPAD_DIVIDE: 111,
NUMPAD_ENTER: 108,
NUMPAD_MULTIPLY: 106,
NUMPAD_SUBTRACT: 109,
PAGE_DOWN: 34,
PAGE_UP: 33,
PERIOD: 190,
RIGHT: 39,
SHIFT: 16,
SPACE: 32,
TAB: 9,
UP: 38,
WINDOWS: 91 // COMMAND
}
});
// plugins
$.fn.extend({
propAttr: $.fn.prop || $.fn.attr,
_focus: $.fn.focus,
focus: function( delay, fn ) {
return typeof delay === "number" ?
this.each(function() {
var elem = this;
setTimeout(function() {
$( elem ).focus();
if ( fn ) {
fn.call( elem );
}
}, delay );
}) :
this._focus.apply( this, arguments );
},
scrollParent: function() {
var scrollParent;
if (($.browser.msie && (/(static|relative)/).test(this.css('position'))) || (/absolute/).test(this.css('position'))) {
scrollParent = this.parents().filter(function() {
return (/(relative|absolute|fixed)/).test($.curCSS(this,'position',1)) && (/(auto|scroll)/).test($.curCSS(this,'overflow',1)+$.curCSS(this,'overflow-y',1)+$.curCSS(this,'overflow-x',1));
}).eq(0);
} else {
scrollParent = this.parents().filter(function() {
return (/(auto|scroll)/).test($.curCSS(this,'overflow',1)+$.curCSS(this,'overflow-y',1)+$.curCSS(this,'overflow-x',1));
}).eq(0);
}
return (/fixed/).test(this.css('position')) || !scrollParent.length ? $(document) : scrollParent;
},
zIndex: function( zIndex ) {
if ( zIndex !== undefined ) {
return this.css( "zIndex", zIndex );
}
if ( this.length ) {
var elem = $( this[ 0 ] ), position, value;
while ( elem.length && elem[ 0 ] !== document ) {
// Ignore z-index if position is set to a value where z-index is ignored by the browser
// This makes behavior of this function consistent across browsers
// WebKit always returns auto if the element is positioned
position = elem.css( "position" );
if ( position === "absolute" || position === "relative" || position === "fixed" ) {
// IE returns 0 when zIndex is not specified
// other browsers return a string
// we ignore the case of nested elements with an explicit value of 0
// <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
value = parseInt( elem.css( "zIndex" ), 10 );
if ( !isNaN( value ) && value !== 0 ) {
return value;
}
}
elem = elem.parent();
}
}
return 0;
},
disableSelection: function() {
return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) +
".ui-disableSelection", function( event ) {
event.preventDefault();
});
},
enableSelection: function() {
return this.unbind( ".ui-disableSelection" );
}
});
$.each( [ "Width", "Height" ], function( i, name ) {
var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
type = name.toLowerCase(),
orig = {
innerWidth: $.fn.innerWidth,
innerHeight: $.fn.innerHeight,
outerWidth: $.fn.outerWidth,
outerHeight: $.fn.outerHeight
};
function reduce( elem, size, border, margin ) {
$.each( side, function() {
size -= parseFloat( $.curCSS( elem, "padding" + this, true) ) || 0;
if ( border ) {
size -= parseFloat( $.curCSS( elem, "border" + this + "Width", true) ) || 0;
}
if ( margin ) {
size -= parseFloat( $.curCSS( elem, "margin" + this, true) ) || 0;
}
});
return size;
}
$.fn[ "inner" + name ] = function( size ) {
if ( size === undefined ) {
return orig[ "inner" + name ].call( this );
}
return this.each(function() {
$( this ).css( type, reduce( this, size ) + "px" );
});
};
$.fn[ "outer" + name] = function( size, margin ) {
if ( typeof size !== "number" ) {
return orig[ "outer" + name ].call( this, size );
}
return this.each(function() {
$( this).css( type, reduce( this, size, true, margin ) + "px" );
});
};
});
// selectors
function focusable( element, isTabIndexNotNaN ) {
var nodeName = element.nodeName.toLowerCase();
if ( "area" === nodeName ) {
var map = element.parentNode,
mapName = map.name,
img;
if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
return false;
}
img = $( "img[usemap=#" + mapName + "]" )[0];
return !!img && visible( img );
}
return ( /input|select|textarea|button|object/.test( nodeName )
? !element.disabled
: "a" == nodeName
? element.href || isTabIndexNotNaN
: isTabIndexNotNaN)
// the element and all of its ancestors must be visible
&& visible( element );
}
function visible( element ) {
return !$( element ).parents().andSelf().filter(function() {
return $.curCSS( this, "visibility" ) === "hidden" ||
$.expr.filters.hidden( this );
}).length;
}
$.extend( $.expr[ ":" ], {
data: function( elem, i, match ) {
return !!$.data( elem, match[ 3 ] );
},
focusable: function( element ) {
return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
},
tabbable: function( element ) {
var tabIndex = $.attr( element, "tabindex" ),
isTabIndexNaN = isNaN( tabIndex );
return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
}
});
// support
$(function() {
var body = document.body,
div = body.appendChild( div = document.createElement( "div" ) );
$.extend( div.style, {
minHeight: "100px",
height: "auto",
padding: 0,
borderWidth: 0
});
$.support.minHeight = div.offsetHeight === 100;
$.support.selectstart = "onselectstart" in div;
// set display to none to avoid a layout bug in IE
// http://dev.jquery.com/ticket/4014
body.removeChild( div ).style.display = "none";
});
// deprecated
$.extend( $.ui, {
// $.ui.plugin is deprecated. Use the proxy pattern instead.
plugin: {
add: function( module, option, set ) {
var proto = $.ui[ module ].prototype;
for ( var i in set ) {
proto.plugins[ i ] = proto.plugins[ i ] || [];
proto.plugins[ i ].push( [ option, set[ i ] ] );
}
},
call: function( instance, name, args ) {
var set = instance.plugins[ name ];
if ( !set || !instance.element[ 0 ].parentNode ) {
return;
}
for ( var i = 0; i < set.length; i++ ) {
if ( instance.options[ set[ i ][ 0 ] ] ) {
set[ i ][ 1 ].apply( instance.element, args );
}
}
}
},
// will be deprecated when we switch to jQuery 1.4 - use jQuery.contains()
contains: function( a, b ) {
return document.compareDocumentPosition ?
a.compareDocumentPosition( b ) & 16 :
a !== b && a.contains( b );
},
// only used by resizable
hasScroll: function( el, a ) {
//If overflow is hidden, the element might have extra content, but the user wants to hide it
if ( $( el ).css( "overflow" ) === "hidden") {
return false;
}
var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop",
has = false;
if ( el[ scroll ] > 0 ) {
return true;
}
// TODO: determine which cases actually cause this to happen
// if the element doesn't have the scroll set, see if it's possible to
// set the scroll
el[ scroll ] = 1;
has = ( el[ scroll ] > 0 );
el[ scroll ] = 0;
return has;
},
// these are odd functions, fix the API or move into individual plugins
isOverAxis: function( x, reference, size ) {
//Determines when x coordinate is over "b" element axis
return ( x > reference ) && ( x < ( reference + size ) );
},
isOver: function( y, x, top, left, height, width ) {
//Determines when x, y coordinates is over "b" element
return $.ui.isOverAxis( y, top, height ) && $.ui.isOverAxis( x, left, width );
}
});
})( jQuery );

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,878 @@
/*
* jQuery UI Dialog 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Dialog
*
* Depends:
* jquery.ui.core.js
* jquery.ui.widget.js
* jquery.ui.button.js
* jquery.ui.draggable.js
* jquery.ui.mouse.js
* jquery.ui.position.js
* jquery.ui.resizable.js
*/
(function( $, undefined ) {
var uiDialogClasses =
'ui-dialog ' +
'ui-widget ' +
'ui-widget-content ' +
'ui-corner-all ',
sizeRelatedOptions = {
buttons: true,
height: true,
maxHeight: true,
maxWidth: true,
minHeight: true,
minWidth: true,
width: true
},
resizableRelatedOptions = {
maxHeight: true,
maxWidth: true,
minHeight: true,
minWidth: true
},
// support for jQuery 1.3.2 - handle common attrFn methods for dialog
attrFn = $.attrFn || {
val: true,
css: true,
html: true,
text: true,
data: true,
width: true,
height: true,
offset: true,
click: true
};
$.widget("ui.dialog", {
options: {
autoOpen: true,
buttons: {},
closeOnEscape: true,
closeText: 'close',
dialogClass: '',
draggable: true,
hide: null,
height: 'auto',
maxHeight: false,
maxWidth: false,
minHeight: 150,
minWidth: 150,
modal: false,
position: {
my: 'center',
at: 'center',
collision: 'fit',
// ensure that the titlebar is never outside the document
using: function(pos) {
var topOffset = $(this).css(pos).offset().top;
if (topOffset < 0) {
$(this).css('top', pos.top - topOffset);
}
}
},
resizable: true,
show: null,
stack: true,
title: '',
width: 300,
zIndex: 1000
},
_create: function() {
this.originalTitle = this.element.attr('title');
// #5742 - .attr() might return a DOMElement
if ( typeof this.originalTitle !== "string" ) {
this.originalTitle = "";
}
this.options.title = this.options.title || this.originalTitle;
var self = this,
options = self.options,
title = options.title || '&#160;',
titleId = $.ui.dialog.getTitleId(self.element),
uiDialog = (self.uiDialog = $('<div></div>'))
.appendTo(document.body)
.hide()
.addClass(uiDialogClasses + options.dialogClass)
.css({
zIndex: options.zIndex
})
// setting tabIndex makes the div focusable
// setting outline to 0 prevents a border on focus in Mozilla
.attr('tabIndex', -1).css('outline', 0).keydown(function(event) {
if (options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode &&
event.keyCode === $.ui.keyCode.ESCAPE) {
self.close(event);
event.preventDefault();
}
})
.attr({
role: 'dialog',
'aria-labelledby': titleId
})
.mousedown(function(event) {
self.moveToTop(false, event);
}),
uiDialogContent = self.element
.show()
.removeAttr('title')
.addClass(
'ui-dialog-content ' +
'ui-widget-content')
.appendTo(uiDialog),
uiDialogTitlebar = (self.uiDialogTitlebar = $('<div></div>'))
.addClass(
'ui-dialog-titlebar ' +
'ui-widget-header ' +
'ui-corner-all ' +
'ui-helper-clearfix'
)
.prependTo(uiDialog),
uiDialogTitlebarClose = $('<a href="#"></a>')
.addClass(
'ui-dialog-titlebar-close ' +
'ui-corner-all'
)
.attr('role', 'button')
.hover(
function() {
uiDialogTitlebarClose.addClass('ui-state-hover');
},
function() {
uiDialogTitlebarClose.removeClass('ui-state-hover');
}
)
.focus(function() {
uiDialogTitlebarClose.addClass('ui-state-focus');
})
.blur(function() {
uiDialogTitlebarClose.removeClass('ui-state-focus');
})
.click(function(event) {
self.close(event);
return false;
})
.appendTo(uiDialogTitlebar),
uiDialogTitlebarCloseText = (self.uiDialogTitlebarCloseText = $('<span></span>'))
.addClass(
'ui-icon ' +
'ui-icon-closethick'
)
.text(options.closeText)
.appendTo(uiDialogTitlebarClose),
uiDialogTitle = $('<span></span>')
.addClass('ui-dialog-title')
.attr('id', titleId)
.html(title)
.prependTo(uiDialogTitlebar);
//handling of deprecated beforeclose (vs beforeClose) option
//Ticket #4669 http://dev.jqueryui.com/ticket/4669
//TODO: remove in 1.9pre
if ($.isFunction(options.beforeclose) && !$.isFunction(options.beforeClose)) {
options.beforeClose = options.beforeclose;
}
uiDialogTitlebar.find("*").add(uiDialogTitlebar).disableSelection();
if (options.draggable && $.fn.draggable) {
self._makeDraggable();
}
if (options.resizable && $.fn.resizable) {
self._makeResizable();
}
self._createButtons(options.buttons);
self._isOpen = false;
if ($.fn.bgiframe) {
uiDialog.bgiframe();
}
},
_init: function() {
if ( this.options.autoOpen ) {
this.open();
}
},
destroy: function() {
var self = this;
if (self.overlay) {
self.overlay.destroy();
}
self.uiDialog.hide();
self.element
.unbind('.dialog')
.removeData('dialog')
.removeClass('ui-dialog-content ui-widget-content')
.hide().appendTo('body');
self.uiDialog.remove();
if (self.originalTitle) {
self.element.attr('title', self.originalTitle);
}
return self;
},
widget: function() {
return this.uiDialog;
},
close: function(event) {
var self = this,
maxZ, thisZ;
if (false === self._trigger('beforeClose', event)) {
return;
}
if (self.overlay) {
self.overlay.destroy();
}
self.uiDialog.unbind('keypress.ui-dialog');
self._isOpen = false;
if (self.options.hide) {
self.uiDialog.hide(self.options.hide, function() {
self._trigger('close', event);
});
} else {
self.uiDialog.hide();
self._trigger('close', event);
}
$.ui.dialog.overlay.resize();
// adjust the maxZ to allow other modal dialogs to continue to work (see #4309)
if (self.options.modal) {
maxZ = 0;
$('.ui-dialog').each(function() {
if (this !== self.uiDialog[0]) {
thisZ = $(this).css('z-index');
if(!isNaN(thisZ)) {
maxZ = Math.max(maxZ, thisZ);
}
}
});
$.ui.dialog.maxZ = maxZ;
}
return self;
},
isOpen: function() {
return this._isOpen;
},
// the force parameter allows us to move modal dialogs to their correct
// position on open
moveToTop: function(force, event) {
var self = this,
options = self.options,
saveScroll;
if ((options.modal && !force) ||
(!options.stack && !options.modal)) {
return self._trigger('focus', event);
}
if (options.zIndex > $.ui.dialog.maxZ) {
$.ui.dialog.maxZ = options.zIndex;
}
if (self.overlay) {
$.ui.dialog.maxZ += 1;
self.overlay.$el.css('z-index', $.ui.dialog.overlay.maxZ = $.ui.dialog.maxZ);
}
//Save and then restore scroll since Opera 9.5+ resets when parent z-Index is changed.
// http://ui.jquery.com/bugs/ticket/3193
saveScroll = { scrollTop: self.element.scrollTop(), scrollLeft: self.element.scrollLeft() };
$.ui.dialog.maxZ += 1;
self.uiDialog.css('z-index', $.ui.dialog.maxZ);
self.element.attr(saveScroll);
self._trigger('focus', event);
return self;
},
open: function() {
if (this._isOpen) { return; }
var self = this,
options = self.options,
uiDialog = self.uiDialog;
self.overlay = options.modal ? new $.ui.dialog.overlay(self) : null;
self._size();
self._position(options.position);
uiDialog.show(options.show);
self.moveToTop(true);
// prevent tabbing out of modal dialogs
if ( options.modal ) {
uiDialog.bind( "keydown.ui-dialog", function( event ) {
if ( event.keyCode !== $.ui.keyCode.TAB ) {
return;
}
var tabbables = $(':tabbable', this),
first = tabbables.filter(':first'),
last = tabbables.filter(':last');
if (event.target === last[0] && !event.shiftKey) {
first.focus(1);
return false;
} else if (event.target === first[0] && event.shiftKey) {
last.focus(1);
return false;
}
});
}
// set focus to the first tabbable element in the content area or the first button
// if there are no tabbable elements, set focus on the dialog itself
$(self.element.find(':tabbable').get().concat(
uiDialog.find('.ui-dialog-buttonpane :tabbable').get().concat(
uiDialog.get()))).eq(0).focus();
self._isOpen = true;
self._trigger('open');
return self;
},
_createButtons: function(buttons) {
var self = this,
hasButtons = false,
uiDialogButtonPane = $('<div></div>')
.addClass(
'ui-dialog-buttonpane ' +
'ui-widget-content ' +
'ui-helper-clearfix'
),
uiButtonSet = $( "<div></div>" )
.addClass( "ui-dialog-buttonset" )
.appendTo( uiDialogButtonPane );
// if we already have a button pane, remove it
self.uiDialog.find('.ui-dialog-buttonpane').remove();
if (typeof buttons === 'object' && buttons !== null) {
$.each(buttons, function() {
return !(hasButtons = true);
});
}
if (hasButtons) {
$.each(buttons, function(name, props) {
props = $.isFunction( props ) ?
{ click: props, text: name } :
props;
var button = $('<button type="button"></button>')
.click(function() {
props.click.apply(self.element[0], arguments);
})
.appendTo(uiButtonSet);
// can't use .attr( props, true ) with jQuery 1.3.2.
$.each( props, function( key, value ) {
if ( key === "click" ) {
return;
}
if ( key in attrFn ) {
button[ key ]( value );
} else {
button.attr( key, value );
}
});
if ($.fn.button) {
button.button();
}
});
uiDialogButtonPane.appendTo(self.uiDialog);
}
},
_makeDraggable: function() {
var self = this,
options = self.options,
doc = $(document),
heightBeforeDrag;
function filteredUi(ui) {
return {
position: ui.position,
offset: ui.offset
};
}
self.uiDialog.draggable({
cancel: '.ui-dialog-content, .ui-dialog-titlebar-close',
handle: '.ui-dialog-titlebar',
containment: 'document',
start: function(event, ui) {
heightBeforeDrag = options.height === "auto" ? "auto" : $(this).height();
$(this).height($(this).height()).addClass("ui-dialog-dragging");
self._trigger('dragStart', event, filteredUi(ui));
},
drag: function(event, ui) {
self._trigger('drag', event, filteredUi(ui));
},
stop: function(event, ui) {
options.position = [ui.position.left - doc.scrollLeft(),
ui.position.top - doc.scrollTop()];
$(this).removeClass("ui-dialog-dragging").height(heightBeforeDrag);
self._trigger('dragStop', event, filteredUi(ui));
$.ui.dialog.overlay.resize();
}
});
},
_makeResizable: function(handles) {
handles = (handles === undefined ? this.options.resizable : handles);
var self = this,
options = self.options,
// .ui-resizable has position: relative defined in the stylesheet
// but dialogs have to use absolute or fixed positioning
position = self.uiDialog.css('position'),
resizeHandles = (typeof handles === 'string' ?
handles :
'n,e,s,w,se,sw,ne,nw'
);
function filteredUi(ui) {
return {
originalPosition: ui.originalPosition,
originalSize: ui.originalSize,
position: ui.position,
size: ui.size
};
}
self.uiDialog.resizable({
cancel: '.ui-dialog-content',
containment: 'document',
alsoResize: self.element,
maxWidth: options.maxWidth,
maxHeight: options.maxHeight,
minWidth: options.minWidth,
minHeight: self._minHeight(),
handles: resizeHandles,
start: function(event, ui) {
$(this).addClass("ui-dialog-resizing");
self._trigger('resizeStart', event, filteredUi(ui));
},
resize: function(event, ui) {
self._trigger('resize', event, filteredUi(ui));
},
stop: function(event, ui) {
$(this).removeClass("ui-dialog-resizing");
options.height = $(this).height();
options.width = $(this).width();
self._trigger('resizeStop', event, filteredUi(ui));
$.ui.dialog.overlay.resize();
}
})
.css('position', position)
.find('.ui-resizable-se').addClass('ui-icon ui-icon-grip-diagonal-se');
},
_minHeight: function() {
var options = this.options;
if (options.height === 'auto') {
return options.minHeight;
} else {
return Math.min(options.minHeight, options.height);
}
},
_position: function(position) {
var myAt = [],
offset = [0, 0],
isVisible;
if (position) {
// deep extending converts arrays to objects in jQuery <= 1.3.2 :-(
// if (typeof position == 'string' || $.isArray(position)) {
// myAt = $.isArray(position) ? position : position.split(' ');
if (typeof position === 'string' || (typeof position === 'object' && '0' in position)) {
myAt = position.split ? position.split(' ') : [position[0], position[1]];
if (myAt.length === 1) {
myAt[1] = myAt[0];
}
$.each(['left', 'top'], function(i, offsetPosition) {
if (+myAt[i] === myAt[i]) {
offset[i] = myAt[i];
myAt[i] = offsetPosition;
}
});
position = {
my: myAt.join(" "),
at: myAt.join(" "),
offset: offset.join(" ")
};
}
position = $.extend({}, $.ui.dialog.prototype.options.position, position);
} else {
position = $.ui.dialog.prototype.options.position;
}
// need to show the dialog to get the actual offset in the position plugin
isVisible = this.uiDialog.is(':visible');
if (!isVisible) {
this.uiDialog.show();
}
this.uiDialog
// workaround for jQuery bug #5781 http://dev.jquery.com/ticket/5781
.css({ top: 0, left: 0 })
.position($.extend({ of: window }, position));
if (!isVisible) {
this.uiDialog.hide();
}
},
_setOptions: function( options ) {
var self = this,
resizableOptions = {},
resize = false;
$.each( options, function( key, value ) {
self._setOption( key, value );
if ( key in sizeRelatedOptions ) {
resize = true;
}
if ( key in resizableRelatedOptions ) {
resizableOptions[ key ] = value;
}
});
if ( resize ) {
this._size();
}
if ( this.uiDialog.is( ":data(resizable)" ) ) {
this.uiDialog.resizable( "option", resizableOptions );
}
},
_setOption: function(key, value){
var self = this,
uiDialog = self.uiDialog;
switch (key) {
//handling of deprecated beforeclose (vs beforeClose) option
//Ticket #4669 http://dev.jqueryui.com/ticket/4669
//TODO: remove in 1.9pre
case "beforeclose":
key = "beforeClose";
break;
case "buttons":
self._createButtons(value);
break;
case "closeText":
// ensure that we always pass a string
self.uiDialogTitlebarCloseText.text("" + value);
break;
case "dialogClass":
uiDialog
.removeClass(self.options.dialogClass)
.addClass(uiDialogClasses + value);
break;
case "disabled":
if (value) {
uiDialog.addClass('ui-dialog-disabled');
} else {
uiDialog.removeClass('ui-dialog-disabled');
}
break;
case "draggable":
var isDraggable = uiDialog.is( ":data(draggable)" );
if ( isDraggable && !value ) {
uiDialog.draggable( "destroy" );
}
if ( !isDraggable && value ) {
self._makeDraggable();
}
break;
case "position":
self._position(value);
break;
case "resizable":
// currently resizable, becoming non-resizable
var isResizable = uiDialog.is( ":data(resizable)" );
if (isResizable && !value) {
uiDialog.resizable('destroy');
}
// currently resizable, changing handles
if (isResizable && typeof value === 'string') {
uiDialog.resizable('option', 'handles', value);
}
// currently non-resizable, becoming resizable
if (!isResizable && value !== false) {
self._makeResizable(value);
}
break;
case "title":
// convert whatever was passed in o a string, for html() to not throw up
$(".ui-dialog-title", self.uiDialogTitlebar).html("" + (value || '&#160;'));
break;
}
$.Widget.prototype._setOption.apply(self, arguments);
},
_size: function() {
/* If the user has resized the dialog, the .ui-dialog and .ui-dialog-content
* divs will both have width and height set, so we need to reset them
*/
var options = this.options,
nonContentHeight,
minContentHeight,
isVisible = this.uiDialog.is( ":visible" );
// reset content sizing
this.element.show().css({
width: 'auto',
minHeight: 0,
height: 0
});
if (options.minWidth > options.width) {
options.width = options.minWidth;
}
// reset wrapper sizing
// determine the height of all the non-content elements
nonContentHeight = this.uiDialog.css({
height: 'auto',
width: options.width
})
.height();
minContentHeight = Math.max( 0, options.minHeight - nonContentHeight );
if ( options.height === "auto" ) {
// only needed for IE6 support
if ( $.support.minHeight ) {
this.element.css({
minHeight: minContentHeight,
height: "auto"
});
} else {
this.uiDialog.show();
var autoHeight = this.element.css( "height", "auto" ).height();
if ( !isVisible ) {
this.uiDialog.hide();
}
this.element.height( Math.max( autoHeight, minContentHeight ) );
}
} else {
this.element.height( Math.max( options.height - nonContentHeight, 0 ) );
}
if (this.uiDialog.is(':data(resizable)')) {
this.uiDialog.resizable('option', 'minHeight', this._minHeight());
}
}
});
$.extend($.ui.dialog, {
version: "1.8.17",
uuid: 0,
maxZ: 0,
getTitleId: function($el) {
var id = $el.attr('id');
if (!id) {
this.uuid += 1;
id = this.uuid;
}
return 'ui-dialog-title-' + id;
},
overlay: function(dialog) {
this.$el = $.ui.dialog.overlay.create(dialog);
}
});
$.extend($.ui.dialog.overlay, {
instances: [],
// reuse old instances due to IE memory leak with alpha transparency (see #5185)
oldInstances: [],
maxZ: 0,
events: $.map('focus,mousedown,mouseup,keydown,keypress,click'.split(','),
function(event) { return event + '.dialog-overlay'; }).join(' '),
create: function(dialog) {
if (this.instances.length === 0) {
// prevent use of anchors and inputs
// we use a setTimeout in case the overlay is created from an
// event that we're going to be cancelling (see #2804)
setTimeout(function() {
// handle $(el).dialog().dialog('close') (see #4065)
if ($.ui.dialog.overlay.instances.length) {
$(document).bind($.ui.dialog.overlay.events, function(event) {
// stop events if the z-index of the target is < the z-index of the overlay
// we cannot return true when we don't want to cancel the event (#3523)
if ($(event.target).zIndex() < $.ui.dialog.overlay.maxZ) {
return false;
}
});
}
}, 1);
// allow closing by pressing the escape key
$(document).bind('keydown.dialog-overlay', function(event) {
if (dialog.options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode &&
event.keyCode === $.ui.keyCode.ESCAPE) {
dialog.close(event);
event.preventDefault();
}
});
// handle window resize
$(window).bind('resize.dialog-overlay', $.ui.dialog.overlay.resize);
}
var $el = (this.oldInstances.pop() || $('<div></div>').addClass('ui-widget-overlay'))
.appendTo(document.body)
.css({
width: this.width(),
height: this.height()
});
if ($.fn.bgiframe) {
$el.bgiframe();
}
this.instances.push($el);
return $el;
},
destroy: function($el) {
var indexOf = $.inArray($el, this.instances);
if (indexOf != -1){
this.oldInstances.push(this.instances.splice(indexOf, 1)[0]);
}
if (this.instances.length === 0) {
$([document, window]).unbind('.dialog-overlay');
}
$el.remove();
// adjust the maxZ to allow other modal dialogs to continue to work (see #4309)
var maxZ = 0;
$.each(this.instances, function() {
maxZ = Math.max(maxZ, this.css('z-index'));
});
this.maxZ = maxZ;
},
height: function() {
var scrollHeight,
offsetHeight;
// handle IE 6
if ($.browser.msie && $.browser.version < 7) {
scrollHeight = Math.max(
document.documentElement.scrollHeight,
document.body.scrollHeight
);
offsetHeight = Math.max(
document.documentElement.offsetHeight,
document.body.offsetHeight
);
if (scrollHeight < offsetHeight) {
return $(window).height() + 'px';
} else {
return scrollHeight + 'px';
}
// handle "good" browsers
} else {
return $(document).height() + 'px';
}
},
width: function() {
var scrollWidth,
offsetWidth;
// handle IE
if ( $.browser.msie ) {
scrollWidth = Math.max(
document.documentElement.scrollWidth,
document.body.scrollWidth
);
offsetWidth = Math.max(
document.documentElement.offsetWidth,
document.body.offsetWidth
);
if (scrollWidth < offsetWidth) {
return $(window).width() + 'px';
} else {
return scrollWidth + 'px';
}
// handle "good" browsers
} else {
return $(document).width() + 'px';
}
},
resize: function() {
/* If the dialog is draggable and the user drags it past the
* right edge of the window, the document becomes wider so we
* need to stretch the overlay. If the user then drags the
* dialog back to the left, the document will become narrower,
* so we need to shrink the overlay to the appropriate size.
* This is handled by shrinking the overlay before setting it
* to the full document size.
*/
var $overlays = $([]);
$.each($.ui.dialog.overlay.instances, function() {
$overlays = $overlays.add(this);
});
$overlays.css({
width: 0,
height: 0
}).css({
width: $.ui.dialog.overlay.width(),
height: $.ui.dialog.overlay.height()
});
}
});
$.extend($.ui.dialog.overlay.prototype, {
destroy: function() {
$.ui.dialog.overlay.destroy(this.$el);
}
});
}(jQuery));

View file

@ -0,0 +1,825 @@
/*
* jQuery UI Draggable 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Draggables
*
* Depends:
* jquery.ui.core.js
* jquery.ui.mouse.js
* jquery.ui.widget.js
*/
(function( $, undefined ) {
$.widget("ui.draggable", $.ui.mouse, {
widgetEventPrefix: "drag",
options: {
addClasses: true,
appendTo: "parent",
axis: false,
connectToSortable: false,
containment: false,
cursor: "auto",
cursorAt: false,
grid: false,
handle: false,
helper: "original",
iframeFix: false,
opacity: false,
refreshPositions: false,
revert: false,
revertDuration: 500,
scope: "default",
scroll: true,
scrollSensitivity: 20,
scrollSpeed: 20,
snap: false,
snapMode: "both",
snapTolerance: 20,
stack: false,
zIndex: false
},
_create: function() {
if (this.options.helper == 'original' && !(/^(?:r|a|f)/).test(this.element.css("position")))
this.element[0].style.position = 'relative';
(this.options.addClasses && this.element.addClass("ui-draggable"));
(this.options.disabled && this.element.addClass("ui-draggable-disabled"));
this._mouseInit();
},
destroy: function() {
if(!this.element.data('draggable')) return;
this.element
.removeData("draggable")
.unbind(".draggable")
.removeClass("ui-draggable"
+ " ui-draggable-dragging"
+ " ui-draggable-disabled");
this._mouseDestroy();
return this;
},
_mouseCapture: function(event) {
var o = this.options;
// among others, prevent a drag on a resizable-handle
if (this.helper || o.disabled || $(event.target).is('.ui-resizable-handle'))
return false;
//Quit if we're not on a valid handle
this.handle = this._getHandle(event);
if (!this.handle)
return false;
if ( o.iframeFix ) {
$(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() {
$('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>')
.css({
width: this.offsetWidth+"px", height: this.offsetHeight+"px",
position: "absolute", opacity: "0.001", zIndex: 1000
})
.css($(this).offset())
.appendTo("body");
});
}
return true;
},
_mouseStart: function(event) {
var o = this.options;
//Create and append the visible helper
this.helper = this._createHelper(event);
//Cache the helper size
this._cacheHelperProportions();
//If ddmanager is used for droppables, set the global draggable
if($.ui.ddmanager)
$.ui.ddmanager.current = this;
/*
* - Position generation -
* This block generates everything position related - it's the core of draggables.
*/
//Cache the margins of the original element
this._cacheMargins();
//Store the helper's css position
this.cssPosition = this.helper.css("position");
this.scrollParent = this.helper.scrollParent();
//The element's absolute position on the page minus margins
this.offset = this.positionAbs = this.element.offset();
this.offset = {
top: this.offset.top - this.margins.top,
left: this.offset.left - this.margins.left
};
$.extend(this.offset, {
click: { //Where the click happened, relative to the element
left: event.pageX - this.offset.left,
top: event.pageY - this.offset.top
},
parent: this._getParentOffset(),
relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
});
//Generate the original position
this.originalPosition = this.position = this._generatePosition(event);
this.originalPageX = event.pageX;
this.originalPageY = event.pageY;
//Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
//Set a containment if given in the options
if(o.containment)
this._setContainment();
//Trigger event + callbacks
if(this._trigger("start", event) === false) {
this._clear();
return false;
}
//Recache the helper size
this._cacheHelperProportions();
//Prepare the droppable offsets
if ($.ui.ddmanager && !o.dropBehaviour)
$.ui.ddmanager.prepareOffsets(this, event);
this.helper.addClass("ui-draggable-dragging");
this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position
//If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
if ( $.ui.ddmanager ) $.ui.ddmanager.dragStart(this, event);
return true;
},
_mouseDrag: function(event, noPropagation) {
//Compute the helpers position
this.position = this._generatePosition(event);
this.positionAbs = this._convertPositionTo("absolute");
//Call plugins and callbacks and use the resulting position if something is returned
if (!noPropagation) {
var ui = this._uiHash();
if(this._trigger('drag', event, ui) === false) {
this._mouseUp({});
return false;
}
this.position = ui.position;
}
if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);
return false;
},
_mouseStop: function(event) {
//If we are using droppables, inform the manager about the drop
var dropped = false;
if ($.ui.ddmanager && !this.options.dropBehaviour)
dropped = $.ui.ddmanager.drop(this, event);
//if a drop comes from outside (a sortable)
if(this.dropped) {
dropped = this.dropped;
this.dropped = false;
}
//if the original element is removed, don't bother to continue if helper is set to "original"
if((!this.element[0] || !this.element[0].parentNode) && this.options.helper == "original")
return false;
if((this.options.revert == "invalid" && !dropped) || (this.options.revert == "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) {
var self = this;
$(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() {
if(self._trigger("stop", event) !== false) {
self._clear();
}
});
} else {
if(this._trigger("stop", event) !== false) {
this._clear();
}
}
return false;
},
_mouseUp: function(event) {
if (this.options.iframeFix === true) {
$("div.ui-draggable-iframeFix").each(function() {
this.parentNode.removeChild(this);
}); //Remove frame helpers
}
//If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003)
if( $.ui.ddmanager ) $.ui.ddmanager.dragStop(this, event);
return $.ui.mouse.prototype._mouseUp.call(this, event);
},
cancel: function() {
if(this.helper.is(".ui-draggable-dragging")) {
this._mouseUp({});
} else {
this._clear();
}
return this;
},
_getHandle: function(event) {
var handle = !this.options.handle || !$(this.options.handle, this.element).length ? true : false;
$(this.options.handle, this.element)
.find("*")
.andSelf()
.each(function() {
if(this == event.target) handle = true;
});
return handle;
},
_createHelper: function(event) {
var o = this.options;
var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event])) : (o.helper == 'clone' ? this.element.clone().removeAttr('id') : this.element);
if(!helper.parents('body').length)
helper.appendTo((o.appendTo == 'parent' ? this.element[0].parentNode : o.appendTo));
if(helper[0] != this.element[0] && !(/(fixed|absolute)/).test(helper.css("position")))
helper.css("position", "absolute");
return helper;
},
_adjustOffsetFromHelper: function(obj) {
if (typeof obj == 'string') {
obj = obj.split(' ');
}
if ($.isArray(obj)) {
obj = {left: +obj[0], top: +obj[1] || 0};
}
if ('left' in obj) {
this.offset.click.left = obj.left + this.margins.left;
}
if ('right' in obj) {
this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
}
if ('top' in obj) {
this.offset.click.top = obj.top + this.margins.top;
}
if ('bottom' in obj) {
this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
}
},
_getParentOffset: function() {
//Get the offsetParent and cache its position
this.offsetParent = this.helper.offsetParent();
var po = this.offsetParent.offset();
// This is a special case where we need to modify a offset calculated on start, since the following happened:
// 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
// the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) {
po.left += this.scrollParent.scrollLeft();
po.top += this.scrollParent.scrollTop();
}
if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information
|| (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.browser.msie)) //Ugly IE fix
po = { top: 0, left: 0 };
return {
top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
};
},
_getRelativeOffset: function() {
if(this.cssPosition == "relative") {
var p = this.element.position();
return {
top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
};
} else {
return { top: 0, left: 0 };
}
},
_cacheMargins: function() {
this.margins = {
left: (parseInt(this.element.css("marginLeft"),10) || 0),
top: (parseInt(this.element.css("marginTop"),10) || 0),
right: (parseInt(this.element.css("marginRight"),10) || 0),
bottom: (parseInt(this.element.css("marginBottom"),10) || 0)
};
},
_cacheHelperProportions: function() {
this.helperProportions = {
width: this.helper.outerWidth(),
height: this.helper.outerHeight()
};
},
_setContainment: function() {
var o = this.options;
if(o.containment == 'parent') o.containment = this.helper[0].parentNode;
if(o.containment == 'document' || o.containment == 'window') this.containment = [
o.containment == 'document' ? 0 : $(window).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
o.containment == 'document' ? 0 : $(window).scrollTop() - this.offset.relative.top - this.offset.parent.top,
(o.containment == 'document' ? 0 : $(window).scrollLeft()) + $(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left,
(o.containment == 'document' ? 0 : $(window).scrollTop()) + ($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
];
if(!(/^(document|window|parent)$/).test(o.containment) && o.containment.constructor != Array) {
var c = $(o.containment);
var ce = c[0]; if(!ce) return;
var co = c.offset();
var over = ($(ce).css("overflow") != 'hidden');
this.containment = [
(parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0),
(parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0),
(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left - this.margins.right,
(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top - this.margins.bottom
];
this.relative_container = c;
} else if(o.containment.constructor == Array) {
this.containment = o.containment;
}
},
_convertPositionTo: function(d, pos) {
if(!pos) pos = this.position;
var mod = d == "absolute" ? 1 : -1;
var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
return {
top: (
pos.top // The absolute mouse position
+ this.offset.relative.top * mod // Only for relative positioned nodes: Relative offset from element to offset parent
+ this.offset.parent.top * mod // The offsetParent's offset without borders (offset + border)
- ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
),
left: (
pos.left // The absolute mouse position
+ this.offset.relative.left * mod // Only for relative positioned nodes: Relative offset from element to offset parent
+ this.offset.parent.left * mod // The offsetParent's offset without borders (offset + border)
- ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
)
};
},
_generatePosition: function(event) {
var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
var pageX = event.pageX;
var pageY = event.pageY;
/*
* - Position constraining -
* Constrain the position to a mix of grid, containment.
*/
if(this.originalPosition) { //If we are not dragging yet, we won't check for options
var containment;
if(this.containment) {
if (this.relative_container){
var co = this.relative_container.offset();
containment = [ this.containment[0] + co.left,
this.containment[1] + co.top,
this.containment[2] + co.left,
this.containment[3] + co.top ];
}
else {
containment = this.containment;
}
if(event.pageX - this.offset.click.left < containment[0]) pageX = containment[0] + this.offset.click.left;
if(event.pageY - this.offset.click.top < containment[1]) pageY = containment[1] + this.offset.click.top;
if(event.pageX - this.offset.click.left > containment[2]) pageX = containment[2] + this.offset.click.left;
if(event.pageY - this.offset.click.top > containment[3]) pageY = containment[3] + this.offset.click.top;
}
if(o.grid) {
//Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950)
var top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY;
pageY = containment ? (!(top - this.offset.click.top < containment[1] || top - this.offset.click.top > containment[3]) ? top : (!(top - this.offset.click.top < containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
var left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX;
pageX = containment ? (!(left - this.offset.click.left < containment[0] || left - this.offset.click.left > containment[2]) ? left : (!(left - this.offset.click.left < containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
}
}
return {
top: (
pageY // The absolute mouse position
- this.offset.click.top // Click offset (relative to the element)
- this.offset.relative.top // Only for relative positioned nodes: Relative offset from element to offset parent
- this.offset.parent.top // The offsetParent's offset without borders (offset + border)
+ ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
),
left: (
pageX // The absolute mouse position
- this.offset.click.left // Click offset (relative to the element)
- this.offset.relative.left // Only for relative positioned nodes: Relative offset from element to offset parent
- this.offset.parent.left // The offsetParent's offset without borders (offset + border)
+ ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
)
};
},
_clear: function() {
this.helper.removeClass("ui-draggable-dragging");
if(this.helper[0] != this.element[0] && !this.cancelHelperRemoval) this.helper.remove();
//if($.ui.ddmanager) $.ui.ddmanager.current = null;
this.helper = null;
this.cancelHelperRemoval = false;
},
// From now on bulk stuff - mainly helpers
_trigger: function(type, event, ui) {
ui = ui || this._uiHash();
$.ui.plugin.call(this, type, [event, ui]);
if(type == "drag") this.positionAbs = this._convertPositionTo("absolute"); //The absolute position has to be recalculated after plugins
return $.Widget.prototype._trigger.call(this, type, event, ui);
},
plugins: {},
_uiHash: function(event) {
return {
helper: this.helper,
position: this.position,
originalPosition: this.originalPosition,
offset: this.positionAbs
};
}
});
$.extend($.ui.draggable, {
version: "1.8.17"
});
$.ui.plugin.add("draggable", "connectToSortable", {
start: function(event, ui) {
var inst = $(this).data("draggable"), o = inst.options,
uiSortable = $.extend({}, ui, { item: inst.element });
inst.sortables = [];
$(o.connectToSortable).each(function() {
var sortable = $.data(this, 'sortable');
if (sortable && !sortable.options.disabled) {
inst.sortables.push({
instance: sortable,
shouldRevert: sortable.options.revert
});
sortable.refreshPositions(); // Call the sortable's refreshPositions at drag start to refresh the containerCache since the sortable container cache is used in drag and needs to be up to date (this will ensure it's initialised as well as being kept in step with any changes that might have happened on the page).
sortable._trigger("activate", event, uiSortable);
}
});
},
stop: function(event, ui) {
//If we are still over the sortable, we fake the stop event of the sortable, but also remove helper
var inst = $(this).data("draggable"),
uiSortable = $.extend({}, ui, { item: inst.element });
$.each(inst.sortables, function() {
if(this.instance.isOver) {
this.instance.isOver = 0;
inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance
this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work)
//The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: 'valid/invalid'
if(this.shouldRevert) this.instance.options.revert = true;
//Trigger the stop of the sortable
this.instance._mouseStop(event);
this.instance.options.helper = this.instance.options._helper;
//If the helper has been the original item, restore properties in the sortable
if(inst.options.helper == 'original')
this.instance.currentItem.css({ top: 'auto', left: 'auto' });
} else {
this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance
this.instance._trigger("deactivate", event, uiSortable);
}
});
},
drag: function(event, ui) {
var inst = $(this).data("draggable"), self = this;
var checkPos = function(o) {
var dyClick = this.offset.click.top, dxClick = this.offset.click.left;
var helperTop = this.positionAbs.top, helperLeft = this.positionAbs.left;
var itemHeight = o.height, itemWidth = o.width;
var itemTop = o.top, itemLeft = o.left;
return $.ui.isOver(helperTop + dyClick, helperLeft + dxClick, itemTop, itemLeft, itemHeight, itemWidth);
};
$.each(inst.sortables, function(i) {
//Copy over some variables to allow calling the sortable's native _intersectsWith
this.instance.positionAbs = inst.positionAbs;
this.instance.helperProportions = inst.helperProportions;
this.instance.offset.click = inst.offset.click;
if(this.instance._intersectsWith(this.instance.containerCache)) {
//If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once
if(!this.instance.isOver) {
this.instance.isOver = 1;
//Now we fake the start of dragging for the sortable instance,
//by cloning the list group item, appending it to the sortable and using it as inst.currentItem
//We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one)
this.instance.currentItem = $(self).clone().removeAttr('id').appendTo(this.instance.element).data("sortable-item", true);
this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it
this.instance.options.helper = function() { return ui.helper[0]; };
event.target = this.instance.currentItem[0];
this.instance._mouseCapture(event, true);
this.instance._mouseStart(event, true, true);
//Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes
this.instance.offset.click.top = inst.offset.click.top;
this.instance.offset.click.left = inst.offset.click.left;
this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left;
this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top;
inst._trigger("toSortable", event);
inst.dropped = this.instance.element; //draggable revert needs that
//hack so receive/update callbacks work (mostly)
inst.currentItem = inst.element;
this.instance.fromOutside = inst;
}
//Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable
if(this.instance.currentItem) this.instance._mouseDrag(event);
} else {
//If it doesn't intersect with the sortable, and it intersected before,
//we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval
if(this.instance.isOver) {
this.instance.isOver = 0;
this.instance.cancelHelperRemoval = true;
//Prevent reverting on this forced stop
this.instance.options.revert = false;
// The out event needs to be triggered independently
this.instance._trigger('out', event, this.instance._uiHash(this.instance));
this.instance._mouseStop(event, true);
this.instance.options.helper = this.instance.options._helper;
//Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size
this.instance.currentItem.remove();
if(this.instance.placeholder) this.instance.placeholder.remove();
inst._trigger("fromSortable", event);
inst.dropped = false; //draggable revert needs that
}
};
});
}
});
$.ui.plugin.add("draggable", "cursor", {
start: function(event, ui) {
var t = $('body'), o = $(this).data('draggable').options;
if (t.css("cursor")) o._cursor = t.css("cursor");
t.css("cursor", o.cursor);
},
stop: function(event, ui) {
var o = $(this).data('draggable').options;
if (o._cursor) $('body').css("cursor", o._cursor);
}
});
$.ui.plugin.add("draggable", "opacity", {
start: function(event, ui) {
var t = $(ui.helper), o = $(this).data('draggable').options;
if(t.css("opacity")) o._opacity = t.css("opacity");
t.css('opacity', o.opacity);
},
stop: function(event, ui) {
var o = $(this).data('draggable').options;
if(o._opacity) $(ui.helper).css('opacity', o._opacity);
}
});
$.ui.plugin.add("draggable", "scroll", {
start: function(event, ui) {
var i = $(this).data("draggable");
if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') i.overflowOffset = i.scrollParent.offset();
},
drag: function(event, ui) {
var i = $(this).data("draggable"), o = i.options, scrolled = false;
if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') {
if(!o.axis || o.axis != 'x') {
if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed;
else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity)
i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed;
}
if(!o.axis || o.axis != 'y') {
if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed;
else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity)
i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed;
}
} else {
if(!o.axis || o.axis != 'x') {
if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
}
if(!o.axis || o.axis != 'y') {
if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
}
}
if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
$.ui.ddmanager.prepareOffsets(i, event);
}
});
$.ui.plugin.add("draggable", "snap", {
start: function(event, ui) {
var i = $(this).data("draggable"), o = i.options;
i.snapElements = [];
$(o.snap.constructor != String ? ( o.snap.items || ':data(draggable)' ) : o.snap).each(function() {
var $t = $(this); var $o = $t.offset();
if(this != i.element[0]) i.snapElements.push({
item: this,
width: $t.outerWidth(), height: $t.outerHeight(),
top: $o.top, left: $o.left
});
});
},
drag: function(event, ui) {
var inst = $(this).data("draggable"), o = inst.options;
var d = o.snapTolerance;
var x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;
for (var i = inst.snapElements.length - 1; i >= 0; i--){
var l = inst.snapElements[i].left, r = l + inst.snapElements[i].width,
t = inst.snapElements[i].top, b = t + inst.snapElements[i].height;
//Yes, I know, this is insane ;)
if(!((l-d < x1 && x1 < r+d && t-d < y1 && y1 < b+d) || (l-d < x1 && x1 < r+d && t-d < y2 && y2 < b+d) || (l-d < x2 && x2 < r+d && t-d < y1 && y1 < b+d) || (l-d < x2 && x2 < r+d && t-d < y2 && y2 < b+d))) {
if(inst.snapElements[i].snapping) (inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
inst.snapElements[i].snapping = false;
continue;
}
if(o.snapMode != 'inner') {
var ts = Math.abs(t - y2) <= d;
var bs = Math.abs(b - y1) <= d;
var ls = Math.abs(l - x2) <= d;
var rs = Math.abs(r - x1) <= d;
if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top;
if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left;
if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left;
}
var first = (ts || bs || ls || rs);
if(o.snapMode != 'outer') {
var ts = Math.abs(t - y1) <= d;
var bs = Math.abs(b - y2) <= d;
var ls = Math.abs(l - x1) <= d;
var rs = Math.abs(r - x2) <= d;
if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top;
if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left;
if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left;
}
if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first))
(inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
inst.snapElements[i].snapping = (ts || bs || ls || rs || first);
};
}
});
$.ui.plugin.add("draggable", "stack", {
start: function(event, ui) {
var o = $(this).data("draggable").options;
var group = $.makeArray($(o.stack)).sort(function(a,b) {
return (parseInt($(a).css("zIndex"),10) || 0) - (parseInt($(b).css("zIndex"),10) || 0);
});
if (!group.length) { return; }
var min = parseInt(group[0].style.zIndex) || 0;
$(group).each(function(i) {
this.style.zIndex = min + i;
});
this[0].style.zIndex = min + group.length;
}
});
$.ui.plugin.add("draggable", "zIndex", {
start: function(event, ui) {
var t = $(ui.helper), o = $(this).data("draggable").options;
if(t.css("zIndex")) o._zIndex = t.css("zIndex");
t.css('zIndex', o.zIndex);
},
stop: function(event, ui) {
var o = $(this).data("draggable").options;
if(o._zIndex) $(ui.helper).css('zIndex', o._zIndex);
}
});
})(jQuery);

View file

@ -0,0 +1,296 @@
/*
* jQuery UI Droppable 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Droppables
*
* Depends:
* jquery.ui.core.js
* jquery.ui.widget.js
* jquery.ui.mouse.js
* jquery.ui.draggable.js
*/
(function( $, undefined ) {
$.widget("ui.droppable", {
widgetEventPrefix: "drop",
options: {
accept: '*',
activeClass: false,
addClasses: true,
greedy: false,
hoverClass: false,
scope: 'default',
tolerance: 'intersect'
},
_create: function() {
var o = this.options, accept = o.accept;
this.isover = 0; this.isout = 1;
this.accept = $.isFunction(accept) ? accept : function(d) {
return d.is(accept);
};
//Store the droppable's proportions
this.proportions = { width: this.element[0].offsetWidth, height: this.element[0].offsetHeight };
// Add the reference and positions to the manager
$.ui.ddmanager.droppables[o.scope] = $.ui.ddmanager.droppables[o.scope] || [];
$.ui.ddmanager.droppables[o.scope].push(this);
(o.addClasses && this.element.addClass("ui-droppable"));
},
destroy: function() {
var drop = $.ui.ddmanager.droppables[this.options.scope];
for ( var i = 0; i < drop.length; i++ )
if ( drop[i] == this )
drop.splice(i, 1);
this.element
.removeClass("ui-droppable ui-droppable-disabled")
.removeData("droppable")
.unbind(".droppable");
return this;
},
_setOption: function(key, value) {
if(key == 'accept') {
this.accept = $.isFunction(value) ? value : function(d) {
return d.is(value);
};
}
$.Widget.prototype._setOption.apply(this, arguments);
},
_activate: function(event) {
var draggable = $.ui.ddmanager.current;
if(this.options.activeClass) this.element.addClass(this.options.activeClass);
(draggable && this._trigger('activate', event, this.ui(draggable)));
},
_deactivate: function(event) {
var draggable = $.ui.ddmanager.current;
if(this.options.activeClass) this.element.removeClass(this.options.activeClass);
(draggable && this._trigger('deactivate', event, this.ui(draggable)));
},
_over: function(event) {
var draggable = $.ui.ddmanager.current;
if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element
if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
if(this.options.hoverClass) this.element.addClass(this.options.hoverClass);
this._trigger('over', event, this.ui(draggable));
}
},
_out: function(event) {
var draggable = $.ui.ddmanager.current;
if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element
if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass);
this._trigger('out', event, this.ui(draggable));
}
},
_drop: function(event,custom) {
var draggable = custom || $.ui.ddmanager.current;
if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return false; // Bail if draggable and droppable are same element
var childrenIntersection = false;
this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function() {
var inst = $.data(this, 'droppable');
if(
inst.options.greedy
&& !inst.options.disabled
&& inst.options.scope == draggable.options.scope
&& inst.accept.call(inst.element[0], (draggable.currentItem || draggable.element))
&& $.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance)
) { childrenIntersection = true; return false; }
});
if(childrenIntersection) return false;
if(this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
if(this.options.activeClass) this.element.removeClass(this.options.activeClass);
if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass);
this._trigger('drop', event, this.ui(draggable));
return this.element;
}
return false;
},
ui: function(c) {
return {
draggable: (c.currentItem || c.element),
helper: c.helper,
position: c.position,
offset: c.positionAbs
};
}
});
$.extend($.ui.droppable, {
version: "1.8.17"
});
$.ui.intersect = function(draggable, droppable, toleranceMode) {
if (!droppable.offset) return false;
var x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width,
y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height;
var l = droppable.offset.left, r = l + droppable.proportions.width,
t = droppable.offset.top, b = t + droppable.proportions.height;
switch (toleranceMode) {
case 'fit':
return (l <= x1 && x2 <= r
&& t <= y1 && y2 <= b);
break;
case 'intersect':
return (l < x1 + (draggable.helperProportions.width / 2) // Right Half
&& x2 - (draggable.helperProportions.width / 2) < r // Left Half
&& t < y1 + (draggable.helperProportions.height / 2) // Bottom Half
&& y2 - (draggable.helperProportions.height / 2) < b ); // Top Half
break;
case 'pointer':
var draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left),
draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top),
isOver = $.ui.isOver(draggableTop, draggableLeft, t, l, droppable.proportions.height, droppable.proportions.width);
return isOver;
break;
case 'touch':
return (
(y1 >= t && y1 <= b) || // Top edge touching
(y2 >= t && y2 <= b) || // Bottom edge touching
(y1 < t && y2 > b) // Surrounded vertically
) && (
(x1 >= l && x1 <= r) || // Left edge touching
(x2 >= l && x2 <= r) || // Right edge touching
(x1 < l && x2 > r) // Surrounded horizontally
);
break;
default:
return false;
break;
}
};
/*
This manager tracks offsets of draggables and droppables
*/
$.ui.ddmanager = {
current: null,
droppables: { 'default': [] },
prepareOffsets: function(t, event) {
var m = $.ui.ddmanager.droppables[t.options.scope] || [];
var type = event ? event.type : null; // workaround for #2317
var list = (t.currentItem || t.element).find(":data(droppable)").andSelf();
droppablesLoop: for (var i = 0; i < m.length; i++) {
if(m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0],(t.currentItem || t.element)))) continue; //No disabled and non-accepted
for (var j=0; j < list.length; j++) { if(list[j] == m[i].element[0]) { m[i].proportions.height = 0; continue droppablesLoop; } }; //Filter out elements in the current dragged item
m[i].visible = m[i].element.css("display") != "none"; if(!m[i].visible) continue; //If the element is not visible, continue
if(type == "mousedown") m[i]._activate.call(m[i], event); //Activate the droppable if used directly from draggables
m[i].offset = m[i].element.offset();
m[i].proportions = { width: m[i].element[0].offsetWidth, height: m[i].element[0].offsetHeight };
}
},
drop: function(draggable, event) {
var dropped = false;
$.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {
if(!this.options) return;
if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance))
dropped = this._drop.call(this, event) || dropped;
if (!this.options.disabled && this.visible && this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
this.isout = 1; this.isover = 0;
this._deactivate.call(this, event);
}
});
return dropped;
},
dragStart: function( draggable, event ) {
//Listen for scrolling so that if the dragging causes scrolling the position of the droppables can be recalculated (see #5003)
draggable.element.parents( ":not(body,html)" ).bind( "scroll.droppable", function() {
if( !draggable.options.refreshPositions ) $.ui.ddmanager.prepareOffsets( draggable, event );
});
},
drag: function(draggable, event) {
//If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse.
if(draggable.options.refreshPositions) $.ui.ddmanager.prepareOffsets(draggable, event);
//Run through all droppables and check their positions based on specific tolerance options
$.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {
if(this.options.disabled || this.greedyChild || !this.visible) return;
var intersects = $.ui.intersect(draggable, this, this.options.tolerance);
var c = !intersects && this.isover == 1 ? 'isout' : (intersects && this.isover == 0 ? 'isover' : null);
if(!c) return;
var parentInstance;
if (this.options.greedy) {
var parent = this.element.parents(':data(droppable):eq(0)');
if (parent.length) {
parentInstance = $.data(parent[0], 'droppable');
parentInstance.greedyChild = (c == 'isover' ? 1 : 0);
}
}
// we just moved into a greedy child
if (parentInstance && c == 'isover') {
parentInstance['isover'] = 0;
parentInstance['isout'] = 1;
parentInstance._out.call(parentInstance, event);
}
this[c] = 1; this[c == 'isout' ? 'isover' : 'isout'] = 0;
this[c == "isover" ? "_over" : "_out"].call(this, event);
// we just moved out of a greedy child
if (parentInstance && c == 'isout') {
parentInstance['isout'] = 0;
parentInstance['isover'] = 1;
parentInstance._over.call(parentInstance, event);
}
});
},
dragStop: function( draggable, event ) {
draggable.element.parents( ":not(body,html)" ).unbind( "scroll.droppable" );
//Call prepareOffsets one final time since IE does not fire return scroll events when overflow was caused by drag (see #5003)
if( !draggable.options.refreshPositions ) $.ui.ddmanager.prepareOffsets( draggable, event );
}
};
})(jQuery);

View file

@ -0,0 +1,162 @@
/*!
* jQuery UI Mouse 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Mouse
*
* Depends:
* jquery.ui.widget.js
*/
(function( $, undefined ) {
var mouseHandled = false;
$( document ).mouseup( function( e ) {
mouseHandled = false;
});
$.widget("ui.mouse", {
options: {
cancel: ':input,option',
distance: 1,
delay: 0
},
_mouseInit: function() {
var self = this;
this.element
.bind('mousedown.'+this.widgetName, function(event) {
return self._mouseDown(event);
})
.bind('click.'+this.widgetName, function(event) {
if (true === $.data(event.target, self.widgetName + '.preventClickEvent')) {
$.removeData(event.target, self.widgetName + '.preventClickEvent');
event.stopImmediatePropagation();
return false;
}
});
this.started = false;
},
// TODO: make sure destroying one instance of mouse doesn't mess with
// other instances of mouse
_mouseDestroy: function() {
this.element.unbind('.'+this.widgetName);
},
_mouseDown: function(event) {
// don't let more than one widget handle mouseStart
if( mouseHandled ) { return };
// we may have missed mouseup (out of window)
(this._mouseStarted && this._mouseUp(event));
this._mouseDownEvent = event;
var self = this,
btnIsLeft = (event.which == 1),
// event.target.nodeName works around a bug in IE 8 with
// disabled inputs (#7620)
elIsCancel = (typeof this.options.cancel == "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false);
if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
return true;
}
this.mouseDelayMet = !this.options.delay;
if (!this.mouseDelayMet) {
this._mouseDelayTimer = setTimeout(function() {
self.mouseDelayMet = true;
}, this.options.delay);
}
if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
this._mouseStarted = (this._mouseStart(event) !== false);
if (!this._mouseStarted) {
event.preventDefault();
return true;
}
}
// Click event may never have fired (Gecko & Opera)
if (true === $.data(event.target, this.widgetName + '.preventClickEvent')) {
$.removeData(event.target, this.widgetName + '.preventClickEvent');
}
// these delegates are required to keep context
this._mouseMoveDelegate = function(event) {
return self._mouseMove(event);
};
this._mouseUpDelegate = function(event) {
return self._mouseUp(event);
};
$(document)
.bind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
.bind('mouseup.'+this.widgetName, this._mouseUpDelegate);
event.preventDefault();
mouseHandled = true;
return true;
},
_mouseMove: function(event) {
// IE mouseup check - mouseup happened when mouse was out of window
if ($.browser.msie && !(document.documentMode >= 9) && !event.button) {
return this._mouseUp(event);
}
if (this._mouseStarted) {
this._mouseDrag(event);
return event.preventDefault();
}
if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
this._mouseStarted =
(this._mouseStart(this._mouseDownEvent, event) !== false);
(this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event));
}
return !this._mouseStarted;
},
_mouseUp: function(event) {
$(document)
.unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
.unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);
if (this._mouseStarted) {
this._mouseStarted = false;
if (event.target == this._mouseDownEvent.target) {
$.data(event.target, this.widgetName + '.preventClickEvent', true);
}
this._mouseStop(event);
}
return false;
},
_mouseDistanceMet: function(event) {
return (Math.max(
Math.abs(this._mouseDownEvent.pageX - event.pageX),
Math.abs(this._mouseDownEvent.pageY - event.pageY)
) >= this.options.distance
);
},
_mouseDelayMet: function(event) {
return this.mouseDelayMet;
},
// These are placeholder methods, to be overriden by extending plugin
_mouseStart: function(event) {},
_mouseDrag: function(event) {},
_mouseStop: function(event) {},
_mouseCapture: function(event) { return true; }
});
})(jQuery);

View file

@ -0,0 +1,298 @@
/*
* jQuery UI Position 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Position
*/
(function( $, undefined ) {
$.ui = $.ui || {};
var horizontalPositions = /left|center|right/,
verticalPositions = /top|center|bottom/,
center = "center",
support = {},
_position = $.fn.position,
_offset = $.fn.offset;
$.fn.position = function( options ) {
if ( !options || !options.of ) {
return _position.apply( this, arguments );
}
// make a copy, we don't want to modify arguments
options = $.extend( {}, options );
var target = $( options.of ),
targetElem = target[0],
collision = ( options.collision || "flip" ).split( " " ),
offset = options.offset ? options.offset.split( " " ) : [ 0, 0 ],
targetWidth,
targetHeight,
basePosition;
if ( targetElem.nodeType === 9 ) {
targetWidth = target.width();
targetHeight = target.height();
basePosition = { top: 0, left: 0 };
// TODO: use $.isWindow() in 1.9
} else if ( targetElem.setTimeout ) {
targetWidth = target.width();
targetHeight = target.height();
basePosition = { top: target.scrollTop(), left: target.scrollLeft() };
} else if ( targetElem.preventDefault ) {
// force left top to allow flipping
options.at = "left top";
targetWidth = targetHeight = 0;
basePosition = { top: options.of.pageY, left: options.of.pageX };
} else {
targetWidth = target.outerWidth();
targetHeight = target.outerHeight();
basePosition = target.offset();
}
// force my and at to have valid horizontal and veritcal positions
// if a value is missing or invalid, it will be converted to center
$.each( [ "my", "at" ], function() {
var pos = ( options[this] || "" ).split( " " );
if ( pos.length === 1) {
pos = horizontalPositions.test( pos[0] ) ?
pos.concat( [center] ) :
verticalPositions.test( pos[0] ) ?
[ center ].concat( pos ) :
[ center, center ];
}
pos[ 0 ] = horizontalPositions.test( pos[0] ) ? pos[ 0 ] : center;
pos[ 1 ] = verticalPositions.test( pos[1] ) ? pos[ 1 ] : center;
options[ this ] = pos;
});
// normalize collision option
if ( collision.length === 1 ) {
collision[ 1 ] = collision[ 0 ];
}
// normalize offset option
offset[ 0 ] = parseInt( offset[0], 10 ) || 0;
if ( offset.length === 1 ) {
offset[ 1 ] = offset[ 0 ];
}
offset[ 1 ] = parseInt( offset[1], 10 ) || 0;
if ( options.at[0] === "right" ) {
basePosition.left += targetWidth;
} else if ( options.at[0] === center ) {
basePosition.left += targetWidth / 2;
}
if ( options.at[1] === "bottom" ) {
basePosition.top += targetHeight;
} else if ( options.at[1] === center ) {
basePosition.top += targetHeight / 2;
}
basePosition.left += offset[ 0 ];
basePosition.top += offset[ 1 ];
return this.each(function() {
var elem = $( this ),
elemWidth = elem.outerWidth(),
elemHeight = elem.outerHeight(),
marginLeft = parseInt( $.curCSS( this, "marginLeft", true ) ) || 0,
marginTop = parseInt( $.curCSS( this, "marginTop", true ) ) || 0,
collisionWidth = elemWidth + marginLeft +
( parseInt( $.curCSS( this, "marginRight", true ) ) || 0 ),
collisionHeight = elemHeight + marginTop +
( parseInt( $.curCSS( this, "marginBottom", true ) ) || 0 ),
position = $.extend( {}, basePosition ),
collisionPosition;
if ( options.my[0] === "right" ) {
position.left -= elemWidth;
} else if ( options.my[0] === center ) {
position.left -= elemWidth / 2;
}
if ( options.my[1] === "bottom" ) {
position.top -= elemHeight;
} else if ( options.my[1] === center ) {
position.top -= elemHeight / 2;
}
// prevent fractions if jQuery version doesn't support them (see #5280)
if ( !support.fractions ) {
position.left = Math.round( position.left );
position.top = Math.round( position.top );
}
collisionPosition = {
left: position.left - marginLeft,
top: position.top - marginTop
};
$.each( [ "left", "top" ], function( i, dir ) {
if ( $.ui.position[ collision[i] ] ) {
$.ui.position[ collision[i] ][ dir ]( position, {
targetWidth: targetWidth,
targetHeight: targetHeight,
elemWidth: elemWidth,
elemHeight: elemHeight,
collisionPosition: collisionPosition,
collisionWidth: collisionWidth,
collisionHeight: collisionHeight,
offset: offset,
my: options.my,
at: options.at
});
}
});
if ( $.fn.bgiframe ) {
elem.bgiframe();
}
elem.offset( $.extend( position, { using: options.using } ) );
});
};
$.ui.position = {
fit: {
left: function( position, data ) {
var win = $( window ),
over = data.collisionPosition.left + data.collisionWidth - win.width() - win.scrollLeft();
position.left = over > 0 ? position.left - over : Math.max( position.left - data.collisionPosition.left, position.left );
},
top: function( position, data ) {
var win = $( window ),
over = data.collisionPosition.top + data.collisionHeight - win.height() - win.scrollTop();
position.top = over > 0 ? position.top - over : Math.max( position.top - data.collisionPosition.top, position.top );
}
},
flip: {
left: function( position, data ) {
if ( data.at[0] === center ) {
return;
}
var win = $( window ),
over = data.collisionPosition.left + data.collisionWidth - win.width() - win.scrollLeft(),
myOffset = data.my[ 0 ] === "left" ?
-data.elemWidth :
data.my[ 0 ] === "right" ?
data.elemWidth :
0,
atOffset = data.at[ 0 ] === "left" ?
data.targetWidth :
-data.targetWidth,
offset = -2 * data.offset[ 0 ];
position.left += data.collisionPosition.left < 0 ?
myOffset + atOffset + offset :
over > 0 ?
myOffset + atOffset + offset :
0;
},
top: function( position, data ) {
if ( data.at[1] === center ) {
return;
}
var win = $( window ),
over = data.collisionPosition.top + data.collisionHeight - win.height() - win.scrollTop(),
myOffset = data.my[ 1 ] === "top" ?
-data.elemHeight :
data.my[ 1 ] === "bottom" ?
data.elemHeight :
0,
atOffset = data.at[ 1 ] === "top" ?
data.targetHeight :
-data.targetHeight,
offset = -2 * data.offset[ 1 ];
position.top += data.collisionPosition.top < 0 ?
myOffset + atOffset + offset :
over > 0 ?
myOffset + atOffset + offset :
0;
}
}
};
// offset setter from jQuery 1.4
if ( !$.offset.setOffset ) {
$.offset.setOffset = function( elem, options ) {
// set position first, in-case top/left are set even on static elem
if ( /static/.test( $.curCSS( elem, "position" ) ) ) {
elem.style.position = "relative";
}
var curElem = $( elem ),
curOffset = curElem.offset(),
curTop = parseInt( $.curCSS( elem, "top", true ), 10 ) || 0,
curLeft = parseInt( $.curCSS( elem, "left", true ), 10) || 0,
props = {
top: (options.top - curOffset.top) + curTop,
left: (options.left - curOffset.left) + curLeft
};
if ( 'using' in options ) {
options.using.call( elem, props );
} else {
curElem.css( props );
}
};
$.fn.offset = function( options ) {
var elem = this[ 0 ];
if ( !elem || !elem.ownerDocument ) { return null; }
if ( options ) {
return this.each(function() {
$.offset.setOffset( this, options );
});
}
return _offset.call( this );
};
}
// fraction support test (older versions of jQuery don't support fractions)
(function () {
var body = document.getElementsByTagName( "body" )[ 0 ],
div = document.createElement( "div" ),
testElement, testElementParent, testElementStyle, offset, offsetTotal;
//Create a "fake body" for testing based on method used in jQuery.support
testElement = document.createElement( body ? "div" : "body" );
testElementStyle = {
visibility: "hidden",
width: 0,
height: 0,
border: 0,
margin: 0,
background: "none"
};
if ( body ) {
jQuery.extend( testElementStyle, {
position: "absolute",
left: "-1000px",
top: "-1000px"
});
}
for ( var i in testElementStyle ) {
testElement.style[ i ] = testElementStyle[ i ];
}
testElement.appendChild( div );
testElementParent = body || document.documentElement;
testElementParent.insertBefore( testElement, testElementParent.firstChild );
div.style.cssText = "position: absolute; left: 10.7432222px; top: 10.432325px; height: 30px; width: 201px;";
offset = $( div ).offset( function( _, offset ) {
return offset;
}).offset();
testElement.innerHTML = "";
testElementParent.removeChild( testElement );
offsetTotal = offset.top + offset.left + ( body ? 2000 : 0 );
support.fractions = offsetTotal > 21 && offsetTotal < 22;
})();
}( jQuery ));

View file

@ -0,0 +1,109 @@
/*
* jQuery UI Progressbar 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Progressbar
*
* Depends:
* jquery.ui.core.js
* jquery.ui.widget.js
*/
(function( $, undefined ) {
$.widget( "ui.progressbar", {
options: {
value: 0,
max: 100
},
min: 0,
_create: function() {
this.element
.addClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" )
.attr({
role: "progressbar",
"aria-valuemin": this.min,
"aria-valuemax": this.options.max,
"aria-valuenow": this._value()
});
this.valueDiv = $( "<div class='ui-progressbar-value ui-widget-header ui-corner-left'></div>" )
.appendTo( this.element );
this.oldValue = this._value();
this._refreshValue();
},
destroy: function() {
this.element
.removeClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" )
.removeAttr( "role" )
.removeAttr( "aria-valuemin" )
.removeAttr( "aria-valuemax" )
.removeAttr( "aria-valuenow" );
this.valueDiv.remove();
$.Widget.prototype.destroy.apply( this, arguments );
},
value: function( newValue ) {
if ( newValue === undefined ) {
return this._value();
}
this._setOption( "value", newValue );
return this;
},
_setOption: function( key, value ) {
if ( key === "value" ) {
this.options.value = value;
this._refreshValue();
if ( this._value() === this.options.max ) {
this._trigger( "complete" );
}
}
$.Widget.prototype._setOption.apply( this, arguments );
},
_value: function() {
var val = this.options.value;
// normalize invalid value
if ( typeof val !== "number" ) {
val = 0;
}
return Math.min( this.options.max, Math.max( this.min, val ) );
},
_percentage: function() {
return 100 * this._value() / this.options.max;
},
_refreshValue: function() {
var value = this.value();
var percentage = this._percentage();
if ( this.oldValue !== value ) {
this.oldValue = value;
this._trigger( "change" );
}
this.valueDiv
.toggle( value > this.min )
.toggleClass( "ui-corner-right", value === this.options.max )
.width( percentage.toFixed(0) + "%" );
this.element.attr( "aria-valuenow", value );
}
});
$.extend( $.ui.progressbar, {
version: "1.8.17"
});
})( jQuery );

View file

@ -0,0 +1,842 @@
/*
* jQuery UI Resizable 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Resizables
*
* Depends:
* jquery.ui.core.js
* jquery.ui.mouse.js
* jquery.ui.widget.js
*/
(function( $, undefined ) {
$.widget("ui.resizable", $.ui.mouse, {
widgetEventPrefix: "resize",
options: {
alsoResize: false,
animate: false,
animateDuration: "slow",
animateEasing: "swing",
aspectRatio: false,
autoHide: false,
containment: false,
ghost: false,
grid: false,
handles: "e,s,se",
helper: false,
maxHeight: null,
maxWidth: null,
minHeight: 10,
minWidth: 10,
zIndex: 1000
},
_create: function() {
var self = this, o = this.options;
this.element.addClass("ui-resizable");
$.extend(this, {
_aspectRatio: !!(o.aspectRatio),
aspectRatio: o.aspectRatio,
originalElement: this.element,
_proportionallyResizeElements: [],
_helper: o.helper || o.ghost || o.animate ? o.helper || 'ui-resizable-helper' : null
});
//Wrap the element if it cannot hold child nodes
if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)) {
//Opera fix for relative positioning
if (/relative/.test(this.element.css('position')) && $.browser.opera)
this.element.css({ position: 'relative', top: 'auto', left: 'auto' });
//Create a wrapper element and set the wrapper to the new current internal element
this.element.wrap(
$('<div class="ui-wrapper" style="overflow: hidden;"></div>').css({
position: this.element.css('position'),
width: this.element.outerWidth(),
height: this.element.outerHeight(),
top: this.element.css('top'),
left: this.element.css('left')
})
);
//Overwrite the original this.element
this.element = this.element.parent().data(
"resizable", this.element.data('resizable')
);
this.elementIsWrapper = true;
//Move margins to the wrapper
this.element.css({ marginLeft: this.originalElement.css("marginLeft"), marginTop: this.originalElement.css("marginTop"), marginRight: this.originalElement.css("marginRight"), marginBottom: this.originalElement.css("marginBottom") });
this.originalElement.css({ marginLeft: 0, marginTop: 0, marginRight: 0, marginBottom: 0});
//Prevent Safari textarea resize
this.originalResizeStyle = this.originalElement.css('resize');
this.originalElement.css('resize', 'none');
//Push the actual element to our proportionallyResize internal array
this._proportionallyResizeElements.push(this.originalElement.css({ position: 'static', zoom: 1, display: 'block' }));
// avoid IE jump (hard set the margin)
this.originalElement.css({ margin: this.originalElement.css('margin') });
// fix handlers offset
this._proportionallyResize();
}
this.handles = o.handles || (!$('.ui-resizable-handle', this.element).length ? "e,s,se" : { n: '.ui-resizable-n', e: '.ui-resizable-e', s: '.ui-resizable-s', w: '.ui-resizable-w', se: '.ui-resizable-se', sw: '.ui-resizable-sw', ne: '.ui-resizable-ne', nw: '.ui-resizable-nw' });
if(this.handles.constructor == String) {
if(this.handles == 'all') this.handles = 'n,e,s,w,se,sw,ne,nw';
var n = this.handles.split(","); this.handles = {};
for(var i = 0; i < n.length; i++) {
var handle = $.trim(n[i]), hname = 'ui-resizable-'+handle;
var axis = $('<div class="ui-resizable-handle ' + hname + '"></div>');
// increase zIndex of sw, se, ne, nw axis
//TODO : this modifies original option
if(/sw|se|ne|nw/.test(handle)) axis.css({ zIndex: ++o.zIndex });
//TODO : What's going on here?
if ('se' == handle) {
axis.addClass('ui-icon ui-icon-gripsmall-diagonal-se');
};
//Insert into internal handles object and append to element
this.handles[handle] = '.ui-resizable-'+handle;
this.element.append(axis);
}
}
this._renderAxis = function(target) {
target = target || this.element;
for(var i in this.handles) {
if(this.handles[i].constructor == String)
this.handles[i] = $(this.handles[i], this.element).show();
//Apply pad to wrapper element, needed to fix axis position (textarea, inputs, scrolls)
if (this.elementIsWrapper && this.originalElement[0].nodeName.match(/textarea|input|select|button/i)) {
var axis = $(this.handles[i], this.element), padWrapper = 0;
//Checking the correct pad and border
padWrapper = /sw|ne|nw|se|n|s/.test(i) ? axis.outerHeight() : axis.outerWidth();
//The padding type i have to apply...
var padPos = [ 'padding',
/ne|nw|n/.test(i) ? 'Top' :
/se|sw|s/.test(i) ? 'Bottom' :
/^e$/.test(i) ? 'Right' : 'Left' ].join("");
target.css(padPos, padWrapper);
this._proportionallyResize();
}
//TODO: What's that good for? There's not anything to be executed left
if(!$(this.handles[i]).length)
continue;
}
};
//TODO: make renderAxis a prototype function
this._renderAxis(this.element);
this._handles = $('.ui-resizable-handle', this.element)
.disableSelection();
//Matching axis name
this._handles.mouseover(function() {
if (!self.resizing) {
if (this.className)
var axis = this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);
//Axis, default = se
self.axis = axis && axis[1] ? axis[1] : 'se';
}
});
//If we want to auto hide the elements
if (o.autoHide) {
this._handles.hide();
$(this.element)
.addClass("ui-resizable-autohide")
.hover(function() {
if (o.disabled) return;
$(this).removeClass("ui-resizable-autohide");
self._handles.show();
},
function(){
if (o.disabled) return;
if (!self.resizing) {
$(this).addClass("ui-resizable-autohide");
self._handles.hide();
}
});
}
//Initialize the mouse interaction
this._mouseInit();
},
destroy: function() {
this._mouseDestroy();
var _destroy = function(exp) {
$(exp).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing")
.removeData("resizable").unbind(".resizable").find('.ui-resizable-handle').remove();
};
//TODO: Unwrap at same DOM position
if (this.elementIsWrapper) {
_destroy(this.element);
var wrapper = this.element;
wrapper.after(
this.originalElement.css({
position: wrapper.css('position'),
width: wrapper.outerWidth(),
height: wrapper.outerHeight(),
top: wrapper.css('top'),
left: wrapper.css('left')
})
).remove();
}
this.originalElement.css('resize', this.originalResizeStyle);
_destroy(this.originalElement);
return this;
},
_mouseCapture: function(event) {
var handle = false;
for (var i in this.handles) {
if ($(this.handles[i])[0] == event.target) {
handle = true;
}
}
return !this.options.disabled && handle;
},
_mouseStart: function(event) {
var o = this.options, iniPos = this.element.position(), el = this.element;
this.resizing = true;
this.documentScroll = { top: $(document).scrollTop(), left: $(document).scrollLeft() };
// bugfix for http://dev.jquery.com/ticket/1749
if (el.is('.ui-draggable') || (/absolute/).test(el.css('position'))) {
el.css({ position: 'absolute', top: iniPos.top, left: iniPos.left });
}
//Opera fixing relative position
if ($.browser.opera && (/relative/).test(el.css('position')))
el.css({ position: 'relative', top: 'auto', left: 'auto' });
this._renderProxy();
var curleft = num(this.helper.css('left')), curtop = num(this.helper.css('top'));
if (o.containment) {
curleft += $(o.containment).scrollLeft() || 0;
curtop += $(o.containment).scrollTop() || 0;
}
//Store needed variables
this.offset = this.helper.offset();
this.position = { left: curleft, top: curtop };
this.size = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() };
this.originalSize = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() };
this.originalPosition = { left: curleft, top: curtop };
this.sizeDiff = { width: el.outerWidth() - el.width(), height: el.outerHeight() - el.height() };
this.originalMousePosition = { left: event.pageX, top: event.pageY };
//Aspect Ratio
this.aspectRatio = (typeof o.aspectRatio == 'number') ? o.aspectRatio : ((this.originalSize.width / this.originalSize.height) || 1);
var cursor = $('.ui-resizable-' + this.axis).css('cursor');
$('body').css('cursor', cursor == 'auto' ? this.axis + '-resize' : cursor);
el.addClass("ui-resizable-resizing");
this._propagate("start", event);
return true;
},
_mouseDrag: function(event) {
//Increase performance, avoid regex
var el = this.helper, o = this.options, props = {},
self = this, smp = this.originalMousePosition, a = this.axis;
var dx = (event.pageX-smp.left)||0, dy = (event.pageY-smp.top)||0;
var trigger = this._change[a];
if (!trigger) return false;
// Calculate the attrs that will be change
var data = trigger.apply(this, [event, dx, dy]), ie6 = $.browser.msie && $.browser.version < 7, csdif = this.sizeDiff;
// Put this in the mouseDrag handler since the user can start pressing shift while resizing
this._updateVirtualBoundaries(event.shiftKey);
if (this._aspectRatio || event.shiftKey)
data = this._updateRatio(data, event);
data = this._respectSize(data, event);
// plugins callbacks need to be called first
this._propagate("resize", event);
el.css({
top: this.position.top + "px", left: this.position.left + "px",
width: this.size.width + "px", height: this.size.height + "px"
});
if (!this._helper && this._proportionallyResizeElements.length)
this._proportionallyResize();
this._updateCache(data);
// calling the user callback at the end
this._trigger('resize', event, this.ui());
return false;
},
_mouseStop: function(event) {
this.resizing = false;
var o = this.options, self = this;
if(this._helper) {
var pr = this._proportionallyResizeElements, ista = pr.length && (/textarea/i).test(pr[0].nodeName),
soffseth = ista && $.ui.hasScroll(pr[0], 'left') /* TODO - jump height */ ? 0 : self.sizeDiff.height,
soffsetw = ista ? 0 : self.sizeDiff.width;
var s = { width: (self.helper.width() - soffsetw), height: (self.helper.height() - soffseth) },
left = (parseInt(self.element.css('left'), 10) + (self.position.left - self.originalPosition.left)) || null,
top = (parseInt(self.element.css('top'), 10) + (self.position.top - self.originalPosition.top)) || null;
if (!o.animate)
this.element.css($.extend(s, { top: top, left: left }));
self.helper.height(self.size.height);
self.helper.width(self.size.width);
if (this._helper && !o.animate) this._proportionallyResize();
}
$('body').css('cursor', 'auto');
this.element.removeClass("ui-resizable-resizing");
this._propagate("stop", event);
if (this._helper) this.helper.remove();
return false;
},
_updateVirtualBoundaries: function(forceAspectRatio) {
var o = this.options, pMinWidth, pMaxWidth, pMinHeight, pMaxHeight, b;
b = {
minWidth: isNumber(o.minWidth) ? o.minWidth : 0,
maxWidth: isNumber(o.maxWidth) ? o.maxWidth : Infinity,
minHeight: isNumber(o.minHeight) ? o.minHeight : 0,
maxHeight: isNumber(o.maxHeight) ? o.maxHeight : Infinity
};
if(this._aspectRatio || forceAspectRatio) {
// We want to create an enclosing box whose aspect ration is the requested one
// First, compute the "projected" size for each dimension based on the aspect ratio and other dimension
pMinWidth = b.minHeight * this.aspectRatio;
pMinHeight = b.minWidth / this.aspectRatio;
pMaxWidth = b.maxHeight * this.aspectRatio;
pMaxHeight = b.maxWidth / this.aspectRatio;
if(pMinWidth > b.minWidth) b.minWidth = pMinWidth;
if(pMinHeight > b.minHeight) b.minHeight = pMinHeight;
if(pMaxWidth < b.maxWidth) b.maxWidth = pMaxWidth;
if(pMaxHeight < b.maxHeight) b.maxHeight = pMaxHeight;
}
this._vBoundaries = b;
},
_updateCache: function(data) {
var o = this.options;
this.offset = this.helper.offset();
if (isNumber(data.left)) this.position.left = data.left;
if (isNumber(data.top)) this.position.top = data.top;
if (isNumber(data.height)) this.size.height = data.height;
if (isNumber(data.width)) this.size.width = data.width;
},
_updateRatio: function(data, event) {
var o = this.options, cpos = this.position, csize = this.size, a = this.axis;
if (isNumber(data.height)) data.width = (data.height * this.aspectRatio);
else if (isNumber(data.width)) data.height = (data.width / this.aspectRatio);
if (a == 'sw') {
data.left = cpos.left + (csize.width - data.width);
data.top = null;
}
if (a == 'nw') {
data.top = cpos.top + (csize.height - data.height);
data.left = cpos.left + (csize.width - data.width);
}
return data;
},
_respectSize: function(data, event) {
var el = this.helper, o = this._vBoundaries, pRatio = this._aspectRatio || event.shiftKey, a = this.axis,
ismaxw = isNumber(data.width) && o.maxWidth && (o.maxWidth < data.width), ismaxh = isNumber(data.height) && o.maxHeight && (o.maxHeight < data.height),
isminw = isNumber(data.width) && o.minWidth && (o.minWidth > data.width), isminh = isNumber(data.height) && o.minHeight && (o.minHeight > data.height);
if (isminw) data.width = o.minWidth;
if (isminh) data.height = o.minHeight;
if (ismaxw) data.width = o.maxWidth;
if (ismaxh) data.height = o.maxHeight;
var dw = this.originalPosition.left + this.originalSize.width, dh = this.position.top + this.size.height;
var cw = /sw|nw|w/.test(a), ch = /nw|ne|n/.test(a);
if (isminw && cw) data.left = dw - o.minWidth;
if (ismaxw && cw) data.left = dw - o.maxWidth;
if (isminh && ch) data.top = dh - o.minHeight;
if (ismaxh && ch) data.top = dh - o.maxHeight;
// fixing jump error on top/left - bug #2330
var isNotwh = !data.width && !data.height;
if (isNotwh && !data.left && data.top) data.top = null;
else if (isNotwh && !data.top && data.left) data.left = null;
return data;
},
_proportionallyResize: function() {
var o = this.options;
if (!this._proportionallyResizeElements.length) return;
var element = this.helper || this.element;
for (var i=0; i < this._proportionallyResizeElements.length; i++) {
var prel = this._proportionallyResizeElements[i];
if (!this.borderDif) {
var b = [prel.css('borderTopWidth'), prel.css('borderRightWidth'), prel.css('borderBottomWidth'), prel.css('borderLeftWidth')],
p = [prel.css('paddingTop'), prel.css('paddingRight'), prel.css('paddingBottom'), prel.css('paddingLeft')];
this.borderDif = $.map(b, function(v, i) {
var border = parseInt(v,10)||0, padding = parseInt(p[i],10)||0;
return border + padding;
});
}
if ($.browser.msie && !(!($(element).is(':hidden') || $(element).parents(':hidden').length)))
continue;
prel.css({
height: (element.height() - this.borderDif[0] - this.borderDif[2]) || 0,
width: (element.width() - this.borderDif[1] - this.borderDif[3]) || 0
});
};
},
_renderProxy: function() {
var el = this.element, o = this.options;
this.elementOffset = el.offset();
if(this._helper) {
this.helper = this.helper || $('<div style="overflow:hidden;"></div>');
// fix ie6 offset TODO: This seems broken
var ie6 = $.browser.msie && $.browser.version < 7, ie6offset = (ie6 ? 1 : 0),
pxyoffset = ( ie6 ? 2 : -1 );
this.helper.addClass(this._helper).css({
width: this.element.outerWidth() + pxyoffset,
height: this.element.outerHeight() + pxyoffset,
position: 'absolute',
left: this.elementOffset.left - ie6offset +'px',
top: this.elementOffset.top - ie6offset +'px',
zIndex: ++o.zIndex //TODO: Don't modify option
});
this.helper
.appendTo("body")
.disableSelection();
} else {
this.helper = this.element;
}
},
_change: {
e: function(event, dx, dy) {
return { width: this.originalSize.width + dx };
},
w: function(event, dx, dy) {
var o = this.options, cs = this.originalSize, sp = this.originalPosition;
return { left: sp.left + dx, width: cs.width - dx };
},
n: function(event, dx, dy) {
var o = this.options, cs = this.originalSize, sp = this.originalPosition;
return { top: sp.top + dy, height: cs.height - dy };
},
s: function(event, dx, dy) {
return { height: this.originalSize.height + dy };
},
se: function(event, dx, dy) {
return $.extend(this._change.s.apply(this, arguments), this._change.e.apply(this, [event, dx, dy]));
},
sw: function(event, dx, dy) {
return $.extend(this._change.s.apply(this, arguments), this._change.w.apply(this, [event, dx, dy]));
},
ne: function(event, dx, dy) {
return $.extend(this._change.n.apply(this, arguments), this._change.e.apply(this, [event, dx, dy]));
},
nw: function(event, dx, dy) {
return $.extend(this._change.n.apply(this, arguments), this._change.w.apply(this, [event, dx, dy]));
}
},
_propagate: function(n, event) {
$.ui.plugin.call(this, n, [event, this.ui()]);
(n != "resize" && this._trigger(n, event, this.ui()));
},
plugins: {},
ui: function() {
return {
originalElement: this.originalElement,
element: this.element,
helper: this.helper,
position: this.position,
size: this.size,
originalSize: this.originalSize,
originalPosition: this.originalPosition
};
}
});
$.extend($.ui.resizable, {
version: "1.8.17"
});
/*
* Resizable Extensions
*/
$.ui.plugin.add("resizable", "alsoResize", {
start: function (event, ui) {
var self = $(this).data("resizable"), o = self.options;
var _store = function (exp) {
$(exp).each(function() {
var el = $(this);
el.data("resizable-alsoresize", {
width: parseInt(el.width(), 10), height: parseInt(el.height(), 10),
left: parseInt(el.css('left'), 10), top: parseInt(el.css('top'), 10),
position: el.css('position') // to reset Opera on stop()
});
});
};
if (typeof(o.alsoResize) == 'object' && !o.alsoResize.parentNode) {
if (o.alsoResize.length) { o.alsoResize = o.alsoResize[0]; _store(o.alsoResize); }
else { $.each(o.alsoResize, function (exp) { _store(exp); }); }
}else{
_store(o.alsoResize);
}
},
resize: function (event, ui) {
var self = $(this).data("resizable"), o = self.options, os = self.originalSize, op = self.originalPosition;
var delta = {
height: (self.size.height - os.height) || 0, width: (self.size.width - os.width) || 0,
top: (self.position.top - op.top) || 0, left: (self.position.left - op.left) || 0
},
_alsoResize = function (exp, c) {
$(exp).each(function() {
var el = $(this), start = $(this).data("resizable-alsoresize"), style = {},
css = c && c.length ? c : el.parents(ui.originalElement[0]).length ? ['width', 'height'] : ['width', 'height', 'top', 'left'];
$.each(css, function (i, prop) {
var sum = (start[prop]||0) + (delta[prop]||0);
if (sum && sum >= 0)
style[prop] = sum || null;
});
// Opera fixing relative position
if ($.browser.opera && /relative/.test(el.css('position'))) {
self._revertToRelativePosition = true;
el.css({ position: 'absolute', top: 'auto', left: 'auto' });
}
el.css(style);
});
};
if (typeof(o.alsoResize) == 'object' && !o.alsoResize.nodeType) {
$.each(o.alsoResize, function (exp, c) { _alsoResize(exp, c); });
}else{
_alsoResize(o.alsoResize);
}
},
stop: function (event, ui) {
var self = $(this).data("resizable"), o = self.options;
var _reset = function (exp) {
$(exp).each(function() {
var el = $(this);
// reset position for Opera - no need to verify it was changed
el.css({ position: el.data("resizable-alsoresize").position });
});
};
if (self._revertToRelativePosition) {
self._revertToRelativePosition = false;
if (typeof(o.alsoResize) == 'object' && !o.alsoResize.nodeType) {
$.each(o.alsoResize, function (exp) { _reset(exp); });
}else{
_reset(o.alsoResize);
}
}
$(this).removeData("resizable-alsoresize");
}
});
$.ui.plugin.add("resizable", "animate", {
stop: function(event, ui) {
var self = $(this).data("resizable"), o = self.options;
var pr = self._proportionallyResizeElements, ista = pr.length && (/textarea/i).test(pr[0].nodeName),
soffseth = ista && $.ui.hasScroll(pr[0], 'left') /* TODO - jump height */ ? 0 : self.sizeDiff.height,
soffsetw = ista ? 0 : self.sizeDiff.width;
var style = { width: (self.size.width - soffsetw), height: (self.size.height - soffseth) },
left = (parseInt(self.element.css('left'), 10) + (self.position.left - self.originalPosition.left)) || null,
top = (parseInt(self.element.css('top'), 10) + (self.position.top - self.originalPosition.top)) || null;
self.element.animate(
$.extend(style, top && left ? { top: top, left: left } : {}), {
duration: o.animateDuration,
easing: o.animateEasing,
step: function() {
var data = {
width: parseInt(self.element.css('width'), 10),
height: parseInt(self.element.css('height'), 10),
top: parseInt(self.element.css('top'), 10),
left: parseInt(self.element.css('left'), 10)
};
if (pr && pr.length) $(pr[0]).css({ width: data.width, height: data.height });
// propagating resize, and updating values for each animation step
self._updateCache(data);
self._propagate("resize", event);
}
}
);
}
});
$.ui.plugin.add("resizable", "containment", {
start: function(event, ui) {
var self = $(this).data("resizable"), o = self.options, el = self.element;
var oc = o.containment, ce = (oc instanceof $) ? oc.get(0) : (/parent/.test(oc)) ? el.parent().get(0) : oc;
if (!ce) return;
self.containerElement = $(ce);
if (/document/.test(oc) || oc == document) {
self.containerOffset = { left: 0, top: 0 };
self.containerPosition = { left: 0, top: 0 };
self.parentData = {
element: $(document), left: 0, top: 0,
width: $(document).width(), height: $(document).height() || document.body.parentNode.scrollHeight
};
}
// i'm a node, so compute top, left, right, bottom
else {
var element = $(ce), p = [];
$([ "Top", "Right", "Left", "Bottom" ]).each(function(i, name) { p[i] = num(element.css("padding" + name)); });
self.containerOffset = element.offset();
self.containerPosition = element.position();
self.containerSize = { height: (element.innerHeight() - p[3]), width: (element.innerWidth() - p[1]) };
var co = self.containerOffset, ch = self.containerSize.height, cw = self.containerSize.width,
width = ($.ui.hasScroll(ce, "left") ? ce.scrollWidth : cw ), height = ($.ui.hasScroll(ce) ? ce.scrollHeight : ch);
self.parentData = {
element: ce, left: co.left, top: co.top, width: width, height: height
};
}
},
resize: function(event, ui) {
var self = $(this).data("resizable"), o = self.options,
ps = self.containerSize, co = self.containerOffset, cs = self.size, cp = self.position,
pRatio = self._aspectRatio || event.shiftKey, cop = { top:0, left:0 }, ce = self.containerElement;
if (ce[0] != document && (/static/).test(ce.css('position'))) cop = co;
if (cp.left < (self._helper ? co.left : 0)) {
self.size.width = self.size.width + (self._helper ? (self.position.left - co.left) : (self.position.left - cop.left));
if (pRatio) self.size.height = self.size.width / o.aspectRatio;
self.position.left = o.helper ? co.left : 0;
}
if (cp.top < (self._helper ? co.top : 0)) {
self.size.height = self.size.height + (self._helper ? (self.position.top - co.top) : self.position.top);
if (pRatio) self.size.width = self.size.height * o.aspectRatio;
self.position.top = self._helper ? co.top : 0;
}
self.offset.left = self.parentData.left+self.position.left;
self.offset.top = self.parentData.top+self.position.top;
var woset = Math.abs( (self._helper ? self.offset.left - cop.left : (self.offset.left - cop.left)) + self.sizeDiff.width ),
hoset = Math.abs( (self._helper ? self.offset.top - cop.top : (self.offset.top - co.top)) + self.sizeDiff.height );
var isParent = self.containerElement.get(0) == self.element.parent().get(0),
isOffsetRelative = /relative|absolute/.test(self.containerElement.css('position'));
if(isParent && isOffsetRelative) woset -= self.parentData.left;
if (woset + self.size.width >= self.parentData.width) {
self.size.width = self.parentData.width - woset;
if (pRatio) self.size.height = self.size.width / self.aspectRatio;
}
if (hoset + self.size.height >= self.parentData.height) {
self.size.height = self.parentData.height - hoset;
if (pRatio) self.size.width = self.size.height * self.aspectRatio;
}
},
stop: function(event, ui){
var self = $(this).data("resizable"), o = self.options, cp = self.position,
co = self.containerOffset, cop = self.containerPosition, ce = self.containerElement;
var helper = $(self.helper), ho = helper.offset(), w = helper.outerWidth() - self.sizeDiff.width, h = helper.outerHeight() - self.sizeDiff.height;
if (self._helper && !o.animate && (/relative/).test(ce.css('position')))
$(this).css({ left: ho.left - cop.left - co.left, width: w, height: h });
if (self._helper && !o.animate && (/static/).test(ce.css('position')))
$(this).css({ left: ho.left - cop.left - co.left, width: w, height: h });
}
});
$.ui.plugin.add("resizable", "ghost", {
start: function(event, ui) {
var self = $(this).data("resizable"), o = self.options, cs = self.size;
self.ghost = self.originalElement.clone();
self.ghost
.css({ opacity: .25, display: 'block', position: 'relative', height: cs.height, width: cs.width, margin: 0, left: 0, top: 0 })
.addClass('ui-resizable-ghost')
.addClass(typeof o.ghost == 'string' ? o.ghost : '');
self.ghost.appendTo(self.helper);
},
resize: function(event, ui){
var self = $(this).data("resizable"), o = self.options;
if (self.ghost) self.ghost.css({ position: 'relative', height: self.size.height, width: self.size.width });
},
stop: function(event, ui){
var self = $(this).data("resizable"), o = self.options;
if (self.ghost && self.helper) self.helper.get(0).removeChild(self.ghost.get(0));
}
});
$.ui.plugin.add("resizable", "grid", {
resize: function(event, ui) {
var self = $(this).data("resizable"), o = self.options, cs = self.size, os = self.originalSize, op = self.originalPosition, a = self.axis, ratio = o._aspectRatio || event.shiftKey;
o.grid = typeof o.grid == "number" ? [o.grid, o.grid] : o.grid;
var ox = Math.round((cs.width - os.width) / (o.grid[0]||1)) * (o.grid[0]||1), oy = Math.round((cs.height - os.height) / (o.grid[1]||1)) * (o.grid[1]||1);
if (/^(se|s|e)$/.test(a)) {
self.size.width = os.width + ox;
self.size.height = os.height + oy;
}
else if (/^(ne)$/.test(a)) {
self.size.width = os.width + ox;
self.size.height = os.height + oy;
self.position.top = op.top - oy;
}
else if (/^(sw)$/.test(a)) {
self.size.width = os.width + ox;
self.size.height = os.height + oy;
self.position.left = op.left - ox;
}
else {
self.size.width = os.width + ox;
self.size.height = os.height + oy;
self.position.top = op.top - oy;
self.position.left = op.left - ox;
}
}
});
var num = function(v) {
return parseInt(v, 10) || 0;
};
var isNumber = function(value) {
return !isNaN(parseInt(value, 10));
};
})(jQuery);

View file

@ -0,0 +1,267 @@
/*
* jQuery UI Selectable 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Selectables
*
* Depends:
* jquery.ui.core.js
* jquery.ui.mouse.js
* jquery.ui.widget.js
*/
(function( $, undefined ) {
$.widget("ui.selectable", $.ui.mouse, {
options: {
appendTo: 'body',
autoRefresh: true,
distance: 0,
filter: '*',
tolerance: 'touch'
},
_create: function() {
var self = this;
this.element.addClass("ui-selectable");
this.dragged = false;
// cache selectee children based on filter
var selectees;
this.refresh = function() {
selectees = $(self.options.filter, self.element[0]);
selectees.addClass("ui-selectee");
selectees.each(function() {
var $this = $(this);
var pos = $this.offset();
$.data(this, "selectable-item", {
element: this,
$element: $this,
left: pos.left,
top: pos.top,
right: pos.left + $this.outerWidth(),
bottom: pos.top + $this.outerHeight(),
startselected: false,
selected: $this.hasClass('ui-selected'),
selecting: $this.hasClass('ui-selecting'),
unselecting: $this.hasClass('ui-unselecting')
});
});
};
this.refresh();
this.selectees = selectees.addClass("ui-selectee");
this._mouseInit();
this.helper = $("<div class='ui-selectable-helper'></div>");
},
destroy: function() {
this.selectees
.removeClass("ui-selectee")
.removeData("selectable-item");
this.element
.removeClass("ui-selectable ui-selectable-disabled")
.removeData("selectable")
.unbind(".selectable");
this._mouseDestroy();
return this;
},
_mouseStart: function(event) {
var self = this;
this.opos = [event.pageX, event.pageY];
if (this.options.disabled)
return;
var options = this.options;
this.selectees = $(options.filter, this.element[0]);
this._trigger("start", event);
$(options.appendTo).append(this.helper);
// position helper (lasso)
this.helper.css({
"left": event.clientX,
"top": event.clientY,
"width": 0,
"height": 0
});
if (options.autoRefresh) {
this.refresh();
}
this.selectees.filter('.ui-selected').each(function() {
var selectee = $.data(this, "selectable-item");
selectee.startselected = true;
if (!event.metaKey && !event.ctrlKey) {
selectee.$element.removeClass('ui-selected');
selectee.selected = false;
selectee.$element.addClass('ui-unselecting');
selectee.unselecting = true;
// selectable UNSELECTING callback
self._trigger("unselecting", event, {
unselecting: selectee.element
});
}
});
$(event.target).parents().andSelf().each(function() {
var selectee = $.data(this, "selectable-item");
if (selectee) {
var doSelect = (!event.metaKey && !event.ctrlKey) || !selectee.$element.hasClass('ui-selected');
selectee.$element
.removeClass(doSelect ? "ui-unselecting" : "ui-selected")
.addClass(doSelect ? "ui-selecting" : "ui-unselecting");
selectee.unselecting = !doSelect;
selectee.selecting = doSelect;
selectee.selected = doSelect;
// selectable (UN)SELECTING callback
if (doSelect) {
self._trigger("selecting", event, {
selecting: selectee.element
});
} else {
self._trigger("unselecting", event, {
unselecting: selectee.element
});
}
return false;
}
});
},
_mouseDrag: function(event) {
var self = this;
this.dragged = true;
if (this.options.disabled)
return;
var options = this.options;
var x1 = this.opos[0], y1 = this.opos[1], x2 = event.pageX, y2 = event.pageY;
if (x1 > x2) { var tmp = x2; x2 = x1; x1 = tmp; }
if (y1 > y2) { var tmp = y2; y2 = y1; y1 = tmp; }
this.helper.css({left: x1, top: y1, width: x2-x1, height: y2-y1});
this.selectees.each(function() {
var selectee = $.data(this, "selectable-item");
//prevent helper from being selected if appendTo: selectable
if (!selectee || selectee.element == self.element[0])
return;
var hit = false;
if (options.tolerance == 'touch') {
hit = ( !(selectee.left > x2 || selectee.right < x1 || selectee.top > y2 || selectee.bottom < y1) );
} else if (options.tolerance == 'fit') {
hit = (selectee.left > x1 && selectee.right < x2 && selectee.top > y1 && selectee.bottom < y2);
}
if (hit) {
// SELECT
if (selectee.selected) {
selectee.$element.removeClass('ui-selected');
selectee.selected = false;
}
if (selectee.unselecting) {
selectee.$element.removeClass('ui-unselecting');
selectee.unselecting = false;
}
if (!selectee.selecting) {
selectee.$element.addClass('ui-selecting');
selectee.selecting = true;
// selectable SELECTING callback
self._trigger("selecting", event, {
selecting: selectee.element
});
}
} else {
// UNSELECT
if (selectee.selecting) {
if ((event.metaKey || event.ctrlKey) && selectee.startselected) {
selectee.$element.removeClass('ui-selecting');
selectee.selecting = false;
selectee.$element.addClass('ui-selected');
selectee.selected = true;
} else {
selectee.$element.removeClass('ui-selecting');
selectee.selecting = false;
if (selectee.startselected) {
selectee.$element.addClass('ui-unselecting');
selectee.unselecting = true;
}
// selectable UNSELECTING callback
self._trigger("unselecting", event, {
unselecting: selectee.element
});
}
}
if (selectee.selected) {
if (!event.metaKey && !event.ctrlKey && !selectee.startselected) {
selectee.$element.removeClass('ui-selected');
selectee.selected = false;
selectee.$element.addClass('ui-unselecting');
selectee.unselecting = true;
// selectable UNSELECTING callback
self._trigger("unselecting", event, {
unselecting: selectee.element
});
}
}
}
});
return false;
},
_mouseStop: function(event) {
var self = this;
this.dragged = false;
var options = this.options;
$('.ui-unselecting', this.element[0]).each(function() {
var selectee = $.data(this, "selectable-item");
selectee.$element.removeClass('ui-unselecting');
selectee.unselecting = false;
selectee.startselected = false;
self._trigger("unselected", event, {
unselected: selectee.element
});
});
$('.ui-selecting', this.element[0]).each(function() {
var selectee = $.data(this, "selectable-item");
selectee.$element.removeClass('ui-selecting').addClass('ui-selected');
selectee.selecting = false;
selectee.selected = true;
selectee.startselected = true;
self._trigger("selected", event, {
selected: selectee.element
});
});
this._trigger("stop", event);
this.helper.remove();
return false;
}
});
$.extend($.ui.selectable, {
version: "1.8.17"
});
})(jQuery);

View file

@ -0,0 +1,666 @@
/*
* jQuery UI Slider 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Slider
*
* Depends:
* jquery.ui.core.js
* jquery.ui.mouse.js
* jquery.ui.widget.js
*/
(function( $, undefined ) {
// number of pages in a slider
// (how many times can you page up/down to go through the whole range)
var numPages = 5;
$.widget( "ui.slider", $.ui.mouse, {
widgetEventPrefix: "slide",
options: {
animate: false,
distance: 0,
max: 100,
min: 0,
orientation: "horizontal",
range: false,
step: 1,
value: 0,
values: null
},
_create: function() {
var self = this,
o = this.options,
existingHandles = this.element.find( ".ui-slider-handle" ).addClass( "ui-state-default ui-corner-all" ),
handle = "<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>",
handleCount = ( o.values && o.values.length ) || 1,
handles = [];
this._keySliding = false;
this._mouseSliding = false;
this._animateOff = true;
this._handleIndex = null;
this._detectOrientation();
this._mouseInit();
this.element
.addClass( "ui-slider" +
" ui-slider-" + this.orientation +
" ui-widget" +
" ui-widget-content" +
" ui-corner-all" +
( o.disabled ? " ui-slider-disabled ui-disabled" : "" ) );
this.range = $([]);
if ( o.range ) {
if ( o.range === true ) {
if ( !o.values ) {
o.values = [ this._valueMin(), this._valueMin() ];
}
if ( o.values.length && o.values.length !== 2 ) {
o.values = [ o.values[0], o.values[0] ];
}
}
this.range = $( "<div></div>" )
.appendTo( this.element )
.addClass( "ui-slider-range" +
// note: this isn't the most fittingly semantic framework class for this element,
// but worked best visually with a variety of themes
" ui-widget-header" +
( ( o.range === "min" || o.range === "max" ) ? " ui-slider-range-" + o.range : "" ) );
}
for ( var i = existingHandles.length; i < handleCount; i += 1 ) {
handles.push( handle );
}
this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( self.element ) );
this.handle = this.handles.eq( 0 );
this.handles.add( this.range ).filter( "a" )
.click(function( event ) {
event.preventDefault();
})
.hover(function() {
if ( !o.disabled ) {
$( this ).addClass( "ui-state-hover" );
}
}, function() {
$( this ).removeClass( "ui-state-hover" );
})
.focus(function() {
if ( !o.disabled ) {
$( ".ui-slider .ui-state-focus" ).removeClass( "ui-state-focus" );
$( this ).addClass( "ui-state-focus" );
} else {
$( this ).blur();
}
})
.blur(function() {
$( this ).removeClass( "ui-state-focus" );
});
this.handles.each(function( i ) {
$( this ).data( "index.ui-slider-handle", i );
});
this.handles
.keydown(function( event ) {
var ret = true,
index = $( this ).data( "index.ui-slider-handle" ),
allowed,
curVal,
newVal,
step;
if ( self.options.disabled ) {
return;
}
switch ( event.keyCode ) {
case $.ui.keyCode.HOME:
case $.ui.keyCode.END:
case $.ui.keyCode.PAGE_UP:
case $.ui.keyCode.PAGE_DOWN:
case $.ui.keyCode.UP:
case $.ui.keyCode.RIGHT:
case $.ui.keyCode.DOWN:
case $.ui.keyCode.LEFT:
ret = false;
if ( !self._keySliding ) {
self._keySliding = true;
$( this ).addClass( "ui-state-active" );
allowed = self._start( event, index );
if ( allowed === false ) {
return;
}
}
break;
}
step = self.options.step;
if ( self.options.values && self.options.values.length ) {
curVal = newVal = self.values( index );
} else {
curVal = newVal = self.value();
}
switch ( event.keyCode ) {
case $.ui.keyCode.HOME:
newVal = self._valueMin();
break;
case $.ui.keyCode.END:
newVal = self._valueMax();
break;
case $.ui.keyCode.PAGE_UP:
newVal = self._trimAlignValue( curVal + ( (self._valueMax() - self._valueMin()) / numPages ) );
break;
case $.ui.keyCode.PAGE_DOWN:
newVal = self._trimAlignValue( curVal - ( (self._valueMax() - self._valueMin()) / numPages ) );
break;
case $.ui.keyCode.UP:
case $.ui.keyCode.RIGHT:
if ( curVal === self._valueMax() ) {
return;
}
newVal = self._trimAlignValue( curVal + step );
break;
case $.ui.keyCode.DOWN:
case $.ui.keyCode.LEFT:
if ( curVal === self._valueMin() ) {
return;
}
newVal = self._trimAlignValue( curVal - step );
break;
}
self._slide( event, index, newVal );
return ret;
})
.keyup(function( event ) {
var index = $( this ).data( "index.ui-slider-handle" );
if ( self._keySliding ) {
self._keySliding = false;
self._stop( event, index );
self._change( event, index );
$( this ).removeClass( "ui-state-active" );
}
});
this._refreshValue();
this._animateOff = false;
},
destroy: function() {
this.handles.remove();
this.range.remove();
this.element
.removeClass( "ui-slider" +
" ui-slider-horizontal" +
" ui-slider-vertical" +
" ui-slider-disabled" +
" ui-widget" +
" ui-widget-content" +
" ui-corner-all" )
.removeData( "slider" )
.unbind( ".slider" );
this._mouseDestroy();
return this;
},
_mouseCapture: function( event ) {
var o = this.options,
position,
normValue,
distance,
closestHandle,
self,
index,
allowed,
offset,
mouseOverHandle;
if ( o.disabled ) {
return false;
}
this.elementSize = {
width: this.element.outerWidth(),
height: this.element.outerHeight()
};
this.elementOffset = this.element.offset();
position = { x: event.pageX, y: event.pageY };
normValue = this._normValueFromMouse( position );
distance = this._valueMax() - this._valueMin() + 1;
self = this;
this.handles.each(function( i ) {
var thisDistance = Math.abs( normValue - self.values(i) );
if ( distance > thisDistance ) {
distance = thisDistance;
closestHandle = $( this );
index = i;
}
});
// workaround for bug #3736 (if both handles of a range are at 0,
// the first is always used as the one with least distance,
// and moving it is obviously prevented by preventing negative ranges)
if( o.range === true && this.values(1) === o.min ) {
index += 1;
closestHandle = $( this.handles[index] );
}
allowed = this._start( event, index );
if ( allowed === false ) {
return false;
}
this._mouseSliding = true;
self._handleIndex = index;
closestHandle
.addClass( "ui-state-active" )
.focus();
offset = closestHandle.offset();
mouseOverHandle = !$( event.target ).parents().andSelf().is( ".ui-slider-handle" );
this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : {
left: event.pageX - offset.left - ( closestHandle.width() / 2 ),
top: event.pageY - offset.top -
( closestHandle.height() / 2 ) -
( parseInt( closestHandle.css("borderTopWidth"), 10 ) || 0 ) -
( parseInt( closestHandle.css("borderBottomWidth"), 10 ) || 0) +
( parseInt( closestHandle.css("marginTop"), 10 ) || 0)
};
if ( !this.handles.hasClass( "ui-state-hover" ) ) {
this._slide( event, index, normValue );
}
this._animateOff = true;
return true;
},
_mouseStart: function( event ) {
return true;
},
_mouseDrag: function( event ) {
var position = { x: event.pageX, y: event.pageY },
normValue = this._normValueFromMouse( position );
this._slide( event, this._handleIndex, normValue );
return false;
},
_mouseStop: function( event ) {
this.handles.removeClass( "ui-state-active" );
this._mouseSliding = false;
this._stop( event, this._handleIndex );
this._change( event, this._handleIndex );
this._handleIndex = null;
this._clickOffset = null;
this._animateOff = false;
return false;
},
_detectOrientation: function() {
this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal";
},
_normValueFromMouse: function( position ) {
var pixelTotal,
pixelMouse,
percentMouse,
valueTotal,
valueMouse;
if ( this.orientation === "horizontal" ) {
pixelTotal = this.elementSize.width;
pixelMouse = position.x - this.elementOffset.left - ( this._clickOffset ? this._clickOffset.left : 0 );
} else {
pixelTotal = this.elementSize.height;
pixelMouse = position.y - this.elementOffset.top - ( this._clickOffset ? this._clickOffset.top : 0 );
}
percentMouse = ( pixelMouse / pixelTotal );
if ( percentMouse > 1 ) {
percentMouse = 1;
}
if ( percentMouse < 0 ) {
percentMouse = 0;
}
if ( this.orientation === "vertical" ) {
percentMouse = 1 - percentMouse;
}
valueTotal = this._valueMax() - this._valueMin();
valueMouse = this._valueMin() + percentMouse * valueTotal;
return this._trimAlignValue( valueMouse );
},
_start: function( event, index ) {
var uiHash = {
handle: this.handles[ index ],
value: this.value()
};
if ( this.options.values && this.options.values.length ) {
uiHash.value = this.values( index );
uiHash.values = this.values();
}
return this._trigger( "start", event, uiHash );
},
_slide: function( event, index, newVal ) {
var otherVal,
newValues,
allowed;
if ( this.options.values && this.options.values.length ) {
otherVal = this.values( index ? 0 : 1 );
if ( ( this.options.values.length === 2 && this.options.range === true ) &&
( ( index === 0 && newVal > otherVal) || ( index === 1 && newVal < otherVal ) )
) {
newVal = otherVal;
}
if ( newVal !== this.values( index ) ) {
newValues = this.values();
newValues[ index ] = newVal;
// A slide can be canceled by returning false from the slide callback
allowed = this._trigger( "slide", event, {
handle: this.handles[ index ],
value: newVal,
values: newValues
} );
otherVal = this.values( index ? 0 : 1 );
if ( allowed !== false ) {
this.values( index, newVal, true );
}
}
} else {
if ( newVal !== this.value() ) {
// A slide can be canceled by returning false from the slide callback
allowed = this._trigger( "slide", event, {
handle: this.handles[ index ],
value: newVal
} );
if ( allowed !== false ) {
this.value( newVal );
}
}
}
},
_stop: function( event, index ) {
var uiHash = {
handle: this.handles[ index ],
value: this.value()
};
if ( this.options.values && this.options.values.length ) {
uiHash.value = this.values( index );
uiHash.values = this.values();
}
this._trigger( "stop", event, uiHash );
},
_change: function( event, index ) {
if ( !this._keySliding && !this._mouseSliding ) {
var uiHash = {
handle: this.handles[ index ],
value: this.value()
};
if ( this.options.values && this.options.values.length ) {
uiHash.value = this.values( index );
uiHash.values = this.values();
}
this._trigger( "change", event, uiHash );
}
},
value: function( newValue ) {
if ( arguments.length ) {
this.options.value = this._trimAlignValue( newValue );
this._refreshValue();
this._change( null, 0 );
return;
}
return this._value();
},
values: function( index, newValue ) {
var vals,
newValues,
i;
if ( arguments.length > 1 ) {
this.options.values[ index ] = this._trimAlignValue( newValue );
this._refreshValue();
this._change( null, index );
return;
}
if ( arguments.length ) {
if ( $.isArray( arguments[ 0 ] ) ) {
vals = this.options.values;
newValues = arguments[ 0 ];
for ( i = 0; i < vals.length; i += 1 ) {
vals[ i ] = this._trimAlignValue( newValues[ i ] );
this._change( null, i );
}
this._refreshValue();
} else {
if ( this.options.values && this.options.values.length ) {
return this._values( index );
} else {
return this.value();
}
}
} else {
return this._values();
}
},
_setOption: function( key, value ) {
var i,
valsLength = 0;
if ( $.isArray( this.options.values ) ) {
valsLength = this.options.values.length;
}
$.Widget.prototype._setOption.apply( this, arguments );
switch ( key ) {
case "disabled":
if ( value ) {
this.handles.filter( ".ui-state-focus" ).blur();
this.handles.removeClass( "ui-state-hover" );
this.handles.propAttr( "disabled", true );
this.element.addClass( "ui-disabled" );
} else {
this.handles.propAttr( "disabled", false );
this.element.removeClass( "ui-disabled" );
}
break;
case "orientation":
this._detectOrientation();
this.element
.removeClass( "ui-slider-horizontal ui-slider-vertical" )
.addClass( "ui-slider-" + this.orientation );
this._refreshValue();
break;
case "value":
this._animateOff = true;
this._refreshValue();
this._change( null, 0 );
this._animateOff = false;
break;
case "values":
this._animateOff = true;
this._refreshValue();
for ( i = 0; i < valsLength; i += 1 ) {
this._change( null, i );
}
this._animateOff = false;
break;
}
},
//internal value getter
// _value() returns value trimmed by min and max, aligned by step
_value: function() {
var val = this.options.value;
val = this._trimAlignValue( val );
return val;
},
//internal values getter
// _values() returns array of values trimmed by min and max, aligned by step
// _values( index ) returns single value trimmed by min and max, aligned by step
_values: function( index ) {
var val,
vals,
i;
if ( arguments.length ) {
val = this.options.values[ index ];
val = this._trimAlignValue( val );
return val;
} else {
// .slice() creates a copy of the array
// this copy gets trimmed by min and max and then returned
vals = this.options.values.slice();
for ( i = 0; i < vals.length; i+= 1) {
vals[ i ] = this._trimAlignValue( vals[ i ] );
}
return vals;
}
},
// returns the step-aligned value that val is closest to, between (inclusive) min and max
_trimAlignValue: function( val ) {
if ( val <= this._valueMin() ) {
return this._valueMin();
}
if ( val >= this._valueMax() ) {
return this._valueMax();
}
var step = ( this.options.step > 0 ) ? this.options.step : 1,
valModStep = (val - this._valueMin()) % step,
alignValue = val - valModStep;
if ( Math.abs(valModStep) * 2 >= step ) {
alignValue += ( valModStep > 0 ) ? step : ( -step );
}
// Since JavaScript has problems with large floats, round
// the final value to 5 digits after the decimal point (see #4124)
return parseFloat( alignValue.toFixed(5) );
},
_valueMin: function() {
return this.options.min;
},
_valueMax: function() {
return this.options.max;
},
_refreshValue: function() {
var oRange = this.options.range,
o = this.options,
self = this,
animate = ( !this._animateOff ) ? o.animate : false,
valPercent,
_set = {},
lastValPercent,
value,
valueMin,
valueMax;
if ( this.options.values && this.options.values.length ) {
this.handles.each(function( i, j ) {
valPercent = ( self.values(i) - self._valueMin() ) / ( self._valueMax() - self._valueMin() ) * 100;
_set[ self.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
$( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
if ( self.options.range === true ) {
if ( self.orientation === "horizontal" ) {
if ( i === 0 ) {
self.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { left: valPercent + "%" }, o.animate );
}
if ( i === 1 ) {
self.range[ animate ? "animate" : "css" ]( { width: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
}
} else {
if ( i === 0 ) {
self.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { bottom: ( valPercent ) + "%" }, o.animate );
}
if ( i === 1 ) {
self.range[ animate ? "animate" : "css" ]( { height: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
}
}
}
lastValPercent = valPercent;
});
} else {
value = this.value();
valueMin = this._valueMin();
valueMax = this._valueMax();
valPercent = ( valueMax !== valueMin ) ?
( value - valueMin ) / ( valueMax - valueMin ) * 100 :
0;
_set[ self.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
if ( oRange === "min" && this.orientation === "horizontal" ) {
this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { width: valPercent + "%" }, o.animate );
}
if ( oRange === "max" && this.orientation === "horizontal" ) {
this.range[ animate ? "animate" : "css" ]( { width: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
}
if ( oRange === "min" && this.orientation === "vertical" ) {
this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { height: valPercent + "%" }, o.animate );
}
if ( oRange === "max" && this.orientation === "vertical" ) {
this.range[ animate ? "animate" : "css" ]( { height: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
}
}
}
});
$.extend( $.ui.slider, {
version: "1.8.17"
});
}(jQuery));

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,758 @@
/*
* jQuery UI Tabs 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Tabs
*
* Depends:
* jquery.ui.core.js
* jquery.ui.widget.js
*/
(function( $, undefined ) {
var tabId = 0,
listId = 0;
function getNextTabId() {
return ++tabId;
}
function getNextListId() {
return ++listId;
}
$.widget( "ui.tabs", {
options: {
add: null,
ajaxOptions: null,
cache: false,
cookie: null, // e.g. { expires: 7, path: '/', domain: 'jquery.com', secure: true }
collapsible: false,
disable: null,
disabled: [],
enable: null,
event: "click",
fx: null, // e.g. { height: 'toggle', opacity: 'toggle', duration: 200 }
idPrefix: "ui-tabs-",
load: null,
panelTemplate: "<div></div>",
remove: null,
select: null,
show: null,
spinner: "<em>Loading&#8230;</em>",
tabTemplate: "<li><a href='#{href}'><span>#{label}</span></a></li>"
},
_create: function() {
this._tabify( true );
},
_setOption: function( key, value ) {
if ( key == "selected" ) {
if (this.options.collapsible && value == this.options.selected ) {
return;
}
this.select( value );
} else {
this.options[ key ] = value;
this._tabify();
}
},
_tabId: function( a ) {
return a.title && a.title.replace( /\s/g, "_" ).replace( /[^\w\u00c0-\uFFFF-]/g, "" ) ||
this.options.idPrefix + getNextTabId();
},
_sanitizeSelector: function( hash ) {
// we need this because an id may contain a ":"
return hash.replace( /:/g, "\\:" );
},
_cookie: function() {
var cookie = this.cookie ||
( this.cookie = this.options.cookie.name || "ui-tabs-" + getNextListId() );
return $.cookie.apply( null, [ cookie ].concat( $.makeArray( arguments ) ) );
},
_ui: function( tab, panel ) {
return {
tab: tab,
panel: panel,
index: this.anchors.index( tab )
};
},
_cleanup: function() {
// restore all former loading tabs labels
this.lis.filter( ".ui-state-processing" )
.removeClass( "ui-state-processing" )
.find( "span:data(label.tabs)" )
.each(function() {
var el = $( this );
el.html( el.data( "label.tabs" ) ).removeData( "label.tabs" );
});
},
_tabify: function( init ) {
var self = this,
o = this.options,
fragmentId = /^#.+/; // Safari 2 reports '#' for an empty hash
this.list = this.element.find( "ol,ul" ).eq( 0 );
this.lis = $( " > li:has(a[href])", this.list );
this.anchors = this.lis.map(function() {
return $( "a", this )[ 0 ];
});
this.panels = $( [] );
this.anchors.each(function( i, a ) {
var href = $( a ).attr( "href" );
// For dynamically created HTML that contains a hash as href IE < 8 expands
// such href to the full page url with hash and then misinterprets tab as ajax.
// Same consideration applies for an added tab with a fragment identifier
// since a[href=#fragment-identifier] does unexpectedly not match.
// Thus normalize href attribute...
var hrefBase = href.split( "#" )[ 0 ],
baseEl;
if ( hrefBase && ( hrefBase === location.toString().split( "#" )[ 0 ] ||
( baseEl = $( "base" )[ 0 ]) && hrefBase === baseEl.href ) ) {
href = a.hash;
a.href = href;
}
// inline tab
if ( fragmentId.test( href ) ) {
self.panels = self.panels.add( self.element.find( self._sanitizeSelector( href ) ) );
// remote tab
// prevent loading the page itself if href is just "#"
} else if ( href && href !== "#" ) {
// required for restore on destroy
$.data( a, "href.tabs", href );
// TODO until #3808 is fixed strip fragment identifier from url
// (IE fails to load from such url)
$.data( a, "load.tabs", href.replace( /#.*$/, "" ) );
var id = self._tabId( a );
a.href = "#" + id;
var $panel = self.element.find( "#" + id );
if ( !$panel.length ) {
$panel = $( o.panelTemplate )
.attr( "id", id )
.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
.insertAfter( self.panels[ i - 1 ] || self.list );
$panel.data( "destroy.tabs", true );
}
self.panels = self.panels.add( $panel );
// invalid tab href
} else {
o.disabled.push( i );
}
});
// initialization from scratch
if ( init ) {
// attach necessary classes for styling
this.element.addClass( "ui-tabs ui-widget ui-widget-content ui-corner-all" );
this.list.addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" );
this.lis.addClass( "ui-state-default ui-corner-top" );
this.panels.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" );
// Selected tab
// use "selected" option or try to retrieve:
// 1. from fragment identifier in url
// 2. from cookie
// 3. from selected class attribute on <li>
if ( o.selected === undefined ) {
if ( location.hash ) {
this.anchors.each(function( i, a ) {
if ( a.hash == location.hash ) {
o.selected = i;
return false;
}
});
}
if ( typeof o.selected !== "number" && o.cookie ) {
o.selected = parseInt( self._cookie(), 10 );
}
if ( typeof o.selected !== "number" && this.lis.filter( ".ui-tabs-selected" ).length ) {
o.selected = this.lis.index( this.lis.filter( ".ui-tabs-selected" ) );
}
o.selected = o.selected || ( this.lis.length ? 0 : -1 );
} else if ( o.selected === null ) { // usage of null is deprecated, TODO remove in next release
o.selected = -1;
}
// sanity check - default to first tab...
o.selected = ( ( o.selected >= 0 && this.anchors[ o.selected ] ) || o.selected < 0 )
? o.selected
: 0;
// Take disabling tabs via class attribute from HTML
// into account and update option properly.
// A selected tab cannot become disabled.
o.disabled = $.unique( o.disabled.concat(
$.map( this.lis.filter( ".ui-state-disabled" ), function( n, i ) {
return self.lis.index( n );
})
) ).sort();
if ( $.inArray( o.selected, o.disabled ) != -1 ) {
o.disabled.splice( $.inArray( o.selected, o.disabled ), 1 );
}
// highlight selected tab
this.panels.addClass( "ui-tabs-hide" );
this.lis.removeClass( "ui-tabs-selected ui-state-active" );
// check for length avoids error when initializing empty list
if ( o.selected >= 0 && this.anchors.length ) {
self.element.find( self._sanitizeSelector( self.anchors[ o.selected ].hash ) ).removeClass( "ui-tabs-hide" );
this.lis.eq( o.selected ).addClass( "ui-tabs-selected ui-state-active" );
// seems to be expected behavior that the show callback is fired
self.element.queue( "tabs", function() {
self._trigger( "show", null,
self._ui( self.anchors[ o.selected ], self.element.find( self._sanitizeSelector( self.anchors[ o.selected ].hash ) )[ 0 ] ) );
});
this.load( o.selected );
}
// clean up to avoid memory leaks in certain versions of IE 6
// TODO: namespace this event
$( window ).bind( "unload", function() {
self.lis.add( self.anchors ).unbind( ".tabs" );
self.lis = self.anchors = self.panels = null;
});
// update selected after add/remove
} else {
o.selected = this.lis.index( this.lis.filter( ".ui-tabs-selected" ) );
}
// update collapsible
// TODO: use .toggleClass()
this.element[ o.collapsible ? "addClass" : "removeClass" ]( "ui-tabs-collapsible" );
// set or update cookie after init and add/remove respectively
if ( o.cookie ) {
this._cookie( o.selected, o.cookie );
}
// disable tabs
for ( var i = 0, li; ( li = this.lis[ i ] ); i++ ) {
$( li )[ $.inArray( i, o.disabled ) != -1 &&
// TODO: use .toggleClass()
!$( li ).hasClass( "ui-tabs-selected" ) ? "addClass" : "removeClass" ]( "ui-state-disabled" );
}
// reset cache if switching from cached to not cached
if ( o.cache === false ) {
this.anchors.removeData( "cache.tabs" );
}
// remove all handlers before, tabify may run on existing tabs after add or option change
this.lis.add( this.anchors ).unbind( ".tabs" );
if ( o.event !== "mouseover" ) {
var addState = function( state, el ) {
if ( el.is( ":not(.ui-state-disabled)" ) ) {
el.addClass( "ui-state-" + state );
}
};
var removeState = function( state, el ) {
el.removeClass( "ui-state-" + state );
};
this.lis.bind( "mouseover.tabs" , function() {
addState( "hover", $( this ) );
});
this.lis.bind( "mouseout.tabs", function() {
removeState( "hover", $( this ) );
});
this.anchors.bind( "focus.tabs", function() {
addState( "focus", $( this ).closest( "li" ) );
});
this.anchors.bind( "blur.tabs", function() {
removeState( "focus", $( this ).closest( "li" ) );
});
}
// set up animations
var hideFx, showFx;
if ( o.fx ) {
if ( $.isArray( o.fx ) ) {
hideFx = o.fx[ 0 ];
showFx = o.fx[ 1 ];
} else {
hideFx = showFx = o.fx;
}
}
// Reset certain styles left over from animation
// and prevent IE's ClearType bug...
function resetStyle( $el, fx ) {
$el.css( "display", "" );
if ( !$.support.opacity && fx.opacity ) {
$el[ 0 ].style.removeAttribute( "filter" );
}
}
// Show a tab...
var showTab = showFx
? function( clicked, $show ) {
$( clicked ).closest( "li" ).addClass( "ui-tabs-selected ui-state-active" );
$show.hide().removeClass( "ui-tabs-hide" ) // avoid flicker that way
.animate( showFx, showFx.duration || "normal", function() {
resetStyle( $show, showFx );
self._trigger( "show", null, self._ui( clicked, $show[ 0 ] ) );
});
}
: function( clicked, $show ) {
$( clicked ).closest( "li" ).addClass( "ui-tabs-selected ui-state-active" );
$show.removeClass( "ui-tabs-hide" );
self._trigger( "show", null, self._ui( clicked, $show[ 0 ] ) );
};
// Hide a tab, $show is optional...
var hideTab = hideFx
? function( clicked, $hide ) {
$hide.animate( hideFx, hideFx.duration || "normal", function() {
self.lis.removeClass( "ui-tabs-selected ui-state-active" );
$hide.addClass( "ui-tabs-hide" );
resetStyle( $hide, hideFx );
self.element.dequeue( "tabs" );
});
}
: function( clicked, $hide, $show ) {
self.lis.removeClass( "ui-tabs-selected ui-state-active" );
$hide.addClass( "ui-tabs-hide" );
self.element.dequeue( "tabs" );
};
// attach tab event handler, unbind to avoid duplicates from former tabifying...
this.anchors.bind( o.event + ".tabs", function() {
var el = this,
$li = $(el).closest( "li" ),
$hide = self.panels.filter( ":not(.ui-tabs-hide)" ),
$show = self.element.find( self._sanitizeSelector( el.hash ) );
// If tab is already selected and not collapsible or tab disabled or
// or is already loading or click callback returns false stop here.
// Check if click handler returns false last so that it is not executed
// for a disabled or loading tab!
if ( ( $li.hasClass( "ui-tabs-selected" ) && !o.collapsible) ||
$li.hasClass( "ui-state-disabled" ) ||
$li.hasClass( "ui-state-processing" ) ||
self.panels.filter( ":animated" ).length ||
self._trigger( "select", null, self._ui( this, $show[ 0 ] ) ) === false ) {
this.blur();
return false;
}
o.selected = self.anchors.index( this );
self.abort();
// if tab may be closed
if ( o.collapsible ) {
if ( $li.hasClass( "ui-tabs-selected" ) ) {
o.selected = -1;
if ( o.cookie ) {
self._cookie( o.selected, o.cookie );
}
self.element.queue( "tabs", function() {
hideTab( el, $hide );
}).dequeue( "tabs" );
this.blur();
return false;
} else if ( !$hide.length ) {
if ( o.cookie ) {
self._cookie( o.selected, o.cookie );
}
self.element.queue( "tabs", function() {
showTab( el, $show );
});
// TODO make passing in node possible, see also http://dev.jqueryui.com/ticket/3171
self.load( self.anchors.index( this ) );
this.blur();
return false;
}
}
if ( o.cookie ) {
self._cookie( o.selected, o.cookie );
}
// show new tab
if ( $show.length ) {
if ( $hide.length ) {
self.element.queue( "tabs", function() {
hideTab( el, $hide );
});
}
self.element.queue( "tabs", function() {
showTab( el, $show );
});
self.load( self.anchors.index( this ) );
} else {
throw "jQuery UI Tabs: Mismatching fragment identifier.";
}
// Prevent IE from keeping other link focussed when using the back button
// and remove dotted border from clicked link. This is controlled via CSS
// in modern browsers; blur() removes focus from address bar in Firefox
// which can become a usability and annoying problem with tabs('rotate').
if ( $.browser.msie ) {
this.blur();
}
});
// disable click in any case
this.anchors.bind( "click.tabs", function(){
return false;
});
},
_getIndex: function( index ) {
// meta-function to give users option to provide a href string instead of a numerical index.
// also sanitizes numerical indexes to valid values.
if ( typeof index == "string" ) {
index = this.anchors.index( this.anchors.filter( "[href$=" + index + "]" ) );
}
return index;
},
destroy: function() {
var o = this.options;
this.abort();
this.element
.unbind( ".tabs" )
.removeClass( "ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible" )
.removeData( "tabs" );
this.list.removeClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" );
this.anchors.each(function() {
var href = $.data( this, "href.tabs" );
if ( href ) {
this.href = href;
}
var $this = $( this ).unbind( ".tabs" );
$.each( [ "href", "load", "cache" ], function( i, prefix ) {
$this.removeData( prefix + ".tabs" );
});
});
this.lis.unbind( ".tabs" ).add( this.panels ).each(function() {
if ( $.data( this, "destroy.tabs" ) ) {
$( this ).remove();
} else {
$( this ).removeClass([
"ui-state-default",
"ui-corner-top",
"ui-tabs-selected",
"ui-state-active",
"ui-state-hover",
"ui-state-focus",
"ui-state-disabled",
"ui-tabs-panel",
"ui-widget-content",
"ui-corner-bottom",
"ui-tabs-hide"
].join( " " ) );
}
});
if ( o.cookie ) {
this._cookie( null, o.cookie );
}
return this;
},
add: function( url, label, index ) {
if ( index === undefined ) {
index = this.anchors.length;
}
var self = this,
o = this.options,
$li = $( o.tabTemplate.replace( /#\{href\}/g, url ).replace( /#\{label\}/g, label ) ),
id = !url.indexOf( "#" ) ? url.replace( "#", "" ) : this._tabId( $( "a", $li )[ 0 ] );
$li.addClass( "ui-state-default ui-corner-top" ).data( "destroy.tabs", true );
// try to find an existing element before creating a new one
var $panel = self.element.find( "#" + id );
if ( !$panel.length ) {
$panel = $( o.panelTemplate )
.attr( "id", id )
.data( "destroy.tabs", true );
}
$panel.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide" );
if ( index >= this.lis.length ) {
$li.appendTo( this.list );
$panel.appendTo( this.list[ 0 ].parentNode );
} else {
$li.insertBefore( this.lis[ index ] );
$panel.insertBefore( this.panels[ index ] );
}
o.disabled = $.map( o.disabled, function( n, i ) {
return n >= index ? ++n : n;
});
this._tabify();
if ( this.anchors.length == 1 ) {
o.selected = 0;
$li.addClass( "ui-tabs-selected ui-state-active" );
$panel.removeClass( "ui-tabs-hide" );
this.element.queue( "tabs", function() {
self._trigger( "show", null, self._ui( self.anchors[ 0 ], self.panels[ 0 ] ) );
});
this.load( 0 );
}
this._trigger( "add", null, this._ui( this.anchors[ index ], this.panels[ index ] ) );
return this;
},
remove: function( index ) {
index = this._getIndex( index );
var o = this.options,
$li = this.lis.eq( index ).remove(),
$panel = this.panels.eq( index ).remove();
// If selected tab was removed focus tab to the right or
// in case the last tab was removed the tab to the left.
if ( $li.hasClass( "ui-tabs-selected" ) && this.anchors.length > 1) {
this.select( index + ( index + 1 < this.anchors.length ? 1 : -1 ) );
}
o.disabled = $.map(
$.grep( o.disabled, function(n, i) {
return n != index;
}),
function( n, i ) {
return n >= index ? --n : n;
});
this._tabify();
this._trigger( "remove", null, this._ui( $li.find( "a" )[ 0 ], $panel[ 0 ] ) );
return this;
},
enable: function( index ) {
index = this._getIndex( index );
var o = this.options;
if ( $.inArray( index, o.disabled ) == -1 ) {
return;
}
this.lis.eq( index ).removeClass( "ui-state-disabled" );
o.disabled = $.grep( o.disabled, function( n, i ) {
return n != index;
});
this._trigger( "enable", null, this._ui( this.anchors[ index ], this.panels[ index ] ) );
return this;
},
disable: function( index ) {
index = this._getIndex( index );
var self = this, o = this.options;
// cannot disable already selected tab
if ( index != o.selected ) {
this.lis.eq( index ).addClass( "ui-state-disabled" );
o.disabled.push( index );
o.disabled.sort();
this._trigger( "disable", null, this._ui( this.anchors[ index ], this.panels[ index ] ) );
}
return this;
},
select: function( index ) {
index = this._getIndex( index );
if ( index == -1 ) {
if ( this.options.collapsible && this.options.selected != -1 ) {
index = this.options.selected;
} else {
return this;
}
}
this.anchors.eq( index ).trigger( this.options.event + ".tabs" );
return this;
},
load: function( index ) {
index = this._getIndex( index );
var self = this,
o = this.options,
a = this.anchors.eq( index )[ 0 ],
url = $.data( a, "load.tabs" );
this.abort();
// not remote or from cache
if ( !url || this.element.queue( "tabs" ).length !== 0 && $.data( a, "cache.tabs" ) ) {
this.element.dequeue( "tabs" );
return;
}
// load remote from here on
this.lis.eq( index ).addClass( "ui-state-processing" );
if ( o.spinner ) {
var span = $( "span", a );
span.data( "label.tabs", span.html() ).html( o.spinner );
}
this.xhr = $.ajax( $.extend( {}, o.ajaxOptions, {
url: url,
success: function( r, s ) {
self.element.find( self._sanitizeSelector( a.hash ) ).html( r );
// take care of tab labels
self._cleanup();
if ( o.cache ) {
$.data( a, "cache.tabs", true );
}
self._trigger( "load", null, self._ui( self.anchors[ index ], self.panels[ index ] ) );
try {
o.ajaxOptions.success( r, s );
}
catch ( e ) {}
},
error: function( xhr, s, e ) {
// take care of tab labels
self._cleanup();
self._trigger( "load", null, self._ui( self.anchors[ index ], self.panels[ index ] ) );
try {
// Passing index avoid a race condition when this method is
// called after the user has selected another tab.
// Pass the anchor that initiated this request allows
// loadError to manipulate the tab content panel via $(a.hash)
o.ajaxOptions.error( xhr, s, index, a );
}
catch ( e ) {}
}
} ) );
// last, so that load event is fired before show...
self.element.dequeue( "tabs" );
return this;
},
abort: function() {
// stop possibly running animations
this.element.queue( [] );
this.panels.stop( false, true );
// "tabs" queue must not contain more than two elements,
// which are the callbacks for the latest clicked tab...
this.element.queue( "tabs", this.element.queue( "tabs" ).splice( -2, 2 ) );
// terminate pending requests from other tabs
if ( this.xhr ) {
this.xhr.abort();
delete this.xhr;
}
// take care of tab labels
this._cleanup();
return this;
},
url: function( index, url ) {
this.anchors.eq( index ).removeData( "cache.tabs" ).data( "load.tabs", url );
return this;
},
length: function() {
return this.anchors.length;
}
});
$.extend( $.ui.tabs, {
version: "1.8.17"
});
/*
* Tabs Extensions
*/
/*
* Rotate
*/
$.extend( $.ui.tabs.prototype, {
rotation: null,
rotate: function( ms, continuing ) {
var self = this,
o = this.options;
var rotate = self._rotate || ( self._rotate = function( e ) {
clearTimeout( self.rotation );
self.rotation = setTimeout(function() {
var t = o.selected;
self.select( ++t < self.anchors.length ? t : 0 );
}, ms );
if ( e ) {
e.stopPropagation();
}
});
var stop = self._unrotate || ( self._unrotate = !continuing
? function(e) {
if (e.clientX) { // in case of a true click
self.rotate(null);
}
}
: function( e ) {
t = o.selected;
rotate();
});
// start rotation
if ( ms ) {
this.element.bind( "tabsshow", rotate );
this.anchors.bind( o.event + ".tabs", stop );
rotate();
// stop rotation
} else {
clearTimeout( self.rotation );
this.element.unbind( "tabsshow", rotate );
this.anchors.unbind( o.event + ".tabs", stop );
delete this._rotate;
delete this._unrotate;
}
return this;
}
});
})( jQuery );

View file

@ -0,0 +1,272 @@
/*!
* jQuery UI Widget 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Widget
*/
(function( $, undefined ) {
// jQuery 1.4+
if ( $.cleanData ) {
var _cleanData = $.cleanData;
$.cleanData = function( elems ) {
for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
try {
$( elem ).triggerHandler( "remove" );
// http://bugs.jquery.com/ticket/8235
} catch( e ) {}
}
_cleanData( elems );
};
} else {
var _remove = $.fn.remove;
$.fn.remove = function( selector, keepData ) {
return this.each(function() {
if ( !keepData ) {
if ( !selector || $.filter( selector, [ this ] ).length ) {
$( "*", this ).add( [ this ] ).each(function() {
try {
$( this ).triggerHandler( "remove" );
// http://bugs.jquery.com/ticket/8235
} catch( e ) {}
});
}
}
return _remove.call( $(this), selector, keepData );
});
};
}
$.widget = function( name, base, prototype ) {
var namespace = name.split( "." )[ 0 ],
fullName;
name = name.split( "." )[ 1 ];
fullName = namespace + "-" + name;
if ( !prototype ) {
prototype = base;
base = $.Widget;
}
// create selector for plugin
$.expr[ ":" ][ fullName ] = function( elem ) {
return !!$.data( elem, name );
};
$[ namespace ] = $[ namespace ] || {};
$[ namespace ][ name ] = function( options, element ) {
// allow instantiation without initializing for simple inheritance
if ( arguments.length ) {
this._createWidget( options, element );
}
};
var basePrototype = new base();
// we need to make the options hash a property directly on the new instance
// otherwise we'll modify the options hash on the prototype that we're
// inheriting from
// $.each( basePrototype, function( key, val ) {
// if ( $.isPlainObject(val) ) {
// basePrototype[ key ] = $.extend( {}, val );
// }
// });
basePrototype.options = $.extend( true, {}, basePrototype.options );
$[ namespace ][ name ].prototype = $.extend( true, basePrototype, {
namespace: namespace,
widgetName: name,
widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name,
widgetBaseClass: fullName
}, prototype );
$.widget.bridge( name, $[ namespace ][ name ] );
};
$.widget.bridge = function( name, object ) {
$.fn[ name ] = function( options ) {
var isMethodCall = typeof options === "string",
args = Array.prototype.slice.call( arguments, 1 ),
returnValue = this;
// allow multiple hashes to be passed on init
options = !isMethodCall && args.length ?
$.extend.apply( null, [ true, options ].concat(args) ) :
options;
// prevent calls to internal methods
if ( isMethodCall && options.charAt( 0 ) === "_" ) {
return returnValue;
}
if ( isMethodCall ) {
this.each(function() {
var instance = $.data( this, name ),
methodValue = instance && $.isFunction( instance[options] ) ?
instance[ options ].apply( instance, args ) :
instance;
// TODO: add this back in 1.9 and use $.error() (see #5972)
// if ( !instance ) {
// throw "cannot call methods on " + name + " prior to initialization; " +
// "attempted to call method '" + options + "'";
// }
// if ( !$.isFunction( instance[options] ) ) {
// throw "no such method '" + options + "' for " + name + " widget instance";
// }
// var methodValue = instance[ options ].apply( instance, args );
if ( methodValue !== instance && methodValue !== undefined ) {
returnValue = methodValue;
return false;
}
});
} else {
this.each(function() {
var instance = $.data( this, name );
if ( instance ) {
instance.option( options || {} )._init();
} else {
$.data( this, name, new object( options, this ) );
}
});
}
return returnValue;
};
};
$.Widget = function( options, element ) {
// allow instantiation without initializing for simple inheritance
if ( arguments.length ) {
this._createWidget( options, element );
}
};
$.Widget.prototype = {
widgetName: "widget",
widgetEventPrefix: "",
options: {
disabled: false
},
_createWidget: function( options, element ) {
// $.widget.bridge stores the plugin instance, but we do it anyway
// so that it's stored even before the _create function runs
$.data( element, this.widgetName, this );
this.element = $( element );
this.options = $.extend( true, {},
this.options,
this._getCreateOptions(),
options );
var self = this;
this.element.bind( "remove." + this.widgetName, function() {
self.destroy();
});
this._create();
this._trigger( "create" );
this._init();
},
_getCreateOptions: function() {
return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
},
_create: function() {},
_init: function() {},
destroy: function() {
this.element
.unbind( "." + this.widgetName )
.removeData( this.widgetName );
this.widget()
.unbind( "." + this.widgetName )
.removeAttr( "aria-disabled" )
.removeClass(
this.widgetBaseClass + "-disabled " +
"ui-state-disabled" );
},
widget: function() {
return this.element;
},
option: function( key, value ) {
var options = key;
if ( arguments.length === 0 ) {
// don't return a reference to the internal hash
return $.extend( {}, this.options );
}
if (typeof key === "string" ) {
if ( value === undefined ) {
return this.options[ key ];
}
options = {};
options[ key ] = value;
}
this._setOptions( options );
return this;
},
_setOptions: function( options ) {
var self = this;
$.each( options, function( key, value ) {
self._setOption( key, value );
});
return this;
},
_setOption: function( key, value ) {
this.options[ key ] = value;
if ( key === "disabled" ) {
this.widget()
[ value ? "addClass" : "removeClass"](
this.widgetBaseClass + "-disabled" + " " +
"ui-state-disabled" )
.attr( "aria-disabled", value );
}
return this;
},
enable: function() {
return this._setOption( "disabled", false );
},
disable: function() {
return this._setOption( "disabled", true );
},
_trigger: function( type, event, data ) {
var prop, orig,
callback = this.options[ type ];
data = data || {};
event = $.Event( event );
event.type = ( type === this.widgetEventPrefix ?
type :
this.widgetEventPrefix + type ).toLowerCase();
// the original event may come from any element
// so we need to reset the target on the new event
event.target = this.element[ 0 ];
// copy original event properties over to the new event
orig = event.originalEvent;
if ( orig ) {
for ( prop in orig ) {
if ( !( prop in event ) ) {
event[ prop ] = orig[ prop ];
}
}
}
this.element.trigger( event, data );
return !( $.isFunction(callback) &&
callback.call( this.element[0], event, data ) === false ||
event.isDefaultPrevented() );
}
};
})( jQuery );

View file

@ -0,0 +1,682 @@
# **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

@ -0,0 +1,32 @@
class App.Model extends Spine.Model
validate: (data = {}) ->
# console.log 'vali', @
# console.log 'vali', params, '@', @
# console.log 'validate', params, @configure_attributes, @, App.User.configure_attributes
# check if @constructor.configure_attributes is used
return if !@constructor.configure_attributes
errors = {}
for attribute in @constructor.configure_attributes
if !attribute.readonly
# check required
if 'null' of attribute && !attribute[null] && !@[attribute.name]
errors[attribute.name] = 'is required'
# check confirm password
if data.form && attribute.type is 'password' && @[attribute.name]
# get confirm password
if @[attribute.name] isnt @["#{attribute.name}_confirm"]
console.log 'aaa', @[attribute.name], @["#{attribute.name}_confirm"], attribute[null]
errors[attribute.name] = 'didn\'t match'
errors["#{attribute.name}_confirm"] = ''
# return error object
for key, msg of errors
# console.log 'e', errors
return errors
return

View file

@ -0,0 +1,12 @@
class App.Group extends App.Model
@configure 'Group', 'name', 'note', 'active'
@extend Spine.Model.Ajax
@configure_attributes = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, 'class': 'xlarge' },
{ name: 'note', display: 'Note', note: 'Notes are visible to agents only, never to customers.', tag: 'textarea', limit: 250, 'null': true, 'class': 'xlarge' },
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 },
{ name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false, 'class': 'xlarge' },
]
@configure_overview = [
'name',
]

View file

@ -0,0 +1,4 @@
class App.History extends App.Model
@configure 'History', 'name'
@extend Spine.Model.Ajax
@url: '/histories'

View file

@ -0,0 +1,4 @@
class App.HistoryAttribute extends App.Model
@configure 'HistoryAttribute', 'name'
@extend Spine.Model.Ajax
@url: '/history_attributes'

View file

@ -0,0 +1,4 @@
class App.HistoryObject extends App.Model
@configure 'HistoryObject', 'name'
@extend Spine.Model.Ajax
@url: '/history_objects'

View file

@ -0,0 +1,4 @@
class App.HistoryType extends App.Model
@configure 'HistoryType', 'name'
@extend Spine.Model.Ajax
@url: '/history_types'

View file

@ -0,0 +1,9 @@
class App.Network extends App.Model
@configure 'Network', 'name', 'note', 'active'
@extend Spine.Model.Ajax
@configure_attributes = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, 'class': 'xlarge' },
{ name: 'note', display: 'Note', note: 'Notes are visible to agents only, never to customers.', tag: 'textarea', limit: 250, 'null': true, 'class': 'xlarge' },
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 },
{ name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false, 'class': 'xlarge' },
]

View file

@ -0,0 +1,3 @@
class App.NetworkCategory extends App.Model
@configure 'NetworkCategory', 'name', 'network_id', 'network_category_type_id', 'network_privacy_id', 'note', 'allow_comments', 'active'
@extend Spine.Model.Ajax

View file

@ -0,0 +1,3 @@
class App.NetworkCategoryType extends App.Model
@configure 'NetworkCategoryType', 'name', 'note', 'active'
@extend Spine.Model.Ajax

View file

@ -0,0 +1,3 @@
class App.NetworkPrivacy extends App.Model
@configure 'NetworkPrivacy', 'name', 'key'
@extend Spine.Model.Ajax

View file

@ -0,0 +1,13 @@
class App.Organization extends App.Model
@configure 'Organization', 'name', 'shared', 'note', 'active'
@extend Spine.Model.Ajax
@configure_attributes = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, 'class': 'xlarge' },
{ name: 'note', display: 'Note', note: 'Notes are visible to agents only, never to customers.', tag: 'textarea', limit: 250, 'null': true, 'class': 'xlarge' },
{ name: 'shared', display: 'Shared organiztion', note: 'Customers in the organiztion can view each other items.', tag: 'boolean', type: 'boolean', 'default': true, 'null': false, 'class': 'xlarge' },
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 },
{ name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false, 'class': 'xlarge' },
]
@configure_overview = [
'name', 'shared',
]

View file

@ -0,0 +1,3 @@
class App.Overview extends Spine.Model
@configure 'Overview', 'name', 'meta', 'condition', 'order', 'view', 'user_id', 'group_ids'
@extend Spine.Model.Ajax

View file

@ -0,0 +1,3 @@
class App.Post extends App.Model
@configure 'Post', 'title', 'content'
@extend Spine.Model.Ajax

View file

@ -0,0 +1,12 @@
class App.Role extends App.Model
@configure 'Role', 'name', 'note', 'active'
@extend Spine.Model.Ajax
@configure_attributes = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, 'class': 'xlarge' },
{ name: 'note', display: 'Note', note: 'Notes are visible to agents only, never to customers.', tag: 'textarea', limit: 250, 'null': true, 'class': 'xlarge' },
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 },
{ name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false, 'class': 'xlarge' },
]
@configure_overview = [
'name',
]

View file

@ -0,0 +1,3 @@
class App.Session extends App.Model
@configure 'Session', 'data'
@extend Spine.Model.Ajax

View file

@ -0,0 +1,3 @@
class App.Setting extends App.Model
@configure 'Setting', 'name', 'state'
@extend Spine.Model.Ajax

View file

@ -0,0 +1,19 @@
class App.Ticket extends App.Model
@configure 'Ticket', 'number', 'title', 'group_id', 'owner_id', 'customer_id', 'ticket_state_id', 'ticket_priority_id'
@extend Spine.Model.Ajax
# @url: '/tickets'
@configure_attributes = [
{ name: 'number', display: '#', tag: 'input', type: 'text', limit: 100, null: true, read_only: true },
{ name: 'customer_id', display: 'Customer', tag: 'input', type: 'text', limit: 100, null: false, class: 'span8', autocapitalize: false, help: 'Select the customer of the Ticket or create one.', link: '<a href="" class="customer_new">&raquo;</a>' },
{ name: 'group_id', display: 'Group', tag: 'select', multiple: false, limit: 100, null: false, class: 'span8', relation: 'Group', },
{ name: 'owner_id', display: 'Owner', tag: 'select', multiple: false, limit: 100, null: true, class: 'span8', relation: 'User', },
{ name: 'title', display: 'Title', tag: 'input', type: 'text', limit: 100, null: false, class: 'span8', },
{ name: 'ticket_state_id', display: 'State', tag: 'select', multiple: false, null: false, relation: 'TicketState', default: 'new', class: 'medium' },
{ name: 'ticket_priority_id', display: 'Priority', tag: 'select', multiple: false, null: false, relation: 'TicketPriority', default: '2 normal', class: 'medium' },
{ name: 'created_at', display: 'Created', tag: 'time', },
{ name: 'last_contact', display: 'Last contact', tag: 'time', null: true },
{ name: 'last_contact_agent', display: 'Last contact (Agent)', tag: 'time', null: true },
{ name: 'last_contact_customer', display: 'Last contact (Customer)', tag: 'time', null: true },
{ name: 'first_response', display: 'First response', tag: 'time', null: true },
{ name: 'close_time', display: 'Close time', tag: 'time', null: true },
]

View file

@ -0,0 +1,15 @@
class App.TicketArticle extends App.Model
@configure 'TicketArticle', 'from', 'to', 'cc', 'subject', 'body', 'ticket_id', 'ticket_article_type_id', 'ticket_article_sender_id', 'internal', 'in_reply_to'
@extend Spine.Model.Ajax
@url: '/ticket_articles'
@configure_attributes = [
{ name: 'ticket_id', display: 'TicketID', null: false, readonly: 1, },
{ name: 'from', display: 'From', tag: 'input', type: 'text', limit: 100, null: false, class: 'span8', },
{ name: 'to', display: 'To', tag: 'input', type: 'text', limit: 100, null: true, class: 'span8', },
{ name: 'cc', display: 'Cc', tag: 'input', type: 'text', limit: 100, null: true, class: 'span8', },
{ name: 'subject', display: 'Subject', tag: 'input', type: 'text', limit: 100, null: true, class: 'span8', },
{ name: 'body', display: 'Text', tag: 'textarea', rows: 5, limit: 100, null: false, class: 'span8', },
{ name: 'ticket_article_type_id', display: 'Type', tag: 'select', multiple: false, null: false, relation: 'TicketArticleType', default: '', class: 'medium' },
{ name: 'ticket_article_sender_id', display: 'Sender', tag: 'select', multiple: false, null: false, relation: 'TicketArticleSender', default: '', class: 'medium' },
{ name: 'internal', display: 'Visability', tag: 'radio', default: false, null: true, options: { true: 'internal', false: 'public' }, class: 'medium' },
]

View file

@ -0,0 +1,4 @@
class App.TicketArticleSender extends App.Model
@configure 'TicketArticleSender', 'name'
@extend Spine.Model.Ajax
@url: '/ticket_article_senders'

View file

@ -0,0 +1,4 @@
class App.TicketArticleType extends App.Model
@configure 'TicketArticleType', 'name'
@extend Spine.Model.Ajax
@url: '/ticket_article_types'

View file

@ -0,0 +1,4 @@
class App.TicketPriority extends App.Model
@configure 'TicketPriority', 'name', 'note', 'active'
@extend Spine.Model.Ajax
@url: '/ticket_priorities'

View file

@ -0,0 +1,4 @@
class App.TicketState extends App.Model
@configure 'TicketState', 'name', 'note', 'active'
@extend Spine.Model.Ajax
@url: '/ticket_states'

View file

@ -0,0 +1,4 @@
class App.TicketStateType extends App.Model
@configure 'TicketStateType', 'name', 'note', 'active'
@extend Spine.Model.Ajax
@url: '/ticket_state_types'

View file

@ -0,0 +1,25 @@
class App.User extends App.Model
@configure 'User', 'login', 'firstname', 'lastname', 'email', 'web', 'password', 'phone', 'fax', 'mobile', 'street', 'zip', 'city', 'country', 'organization_id', 'note', 'role_ids', 'group_ids', 'active', 'invite'
@extend Spine.Model.Ajax
# @hasMany 'roles', 'App.Role'
@configure_attributes = [
{ name: 'login', display: 'Login', tag: 'input', type: 'text', limit: 100, null: false, class: 'xlarge', autocapitalize: false, signup: false, quick: false },
{ name: 'firstname', display: 'Firstname', tag: 'input', type: 'text', limit: 100, null: false, class: 'xlarge', signup: true, quick: true, info: true, invite_agent: true },
{ name: 'lastname', display: 'Lastname', tag: 'input', type: 'text', limit: 100, null: false, class: 'xlarge', signup: true, quick: true, info: true, invite_agent: true },
{ name: 'email', display: 'Email', tag: 'input', type: 'email', limit: 100, null: false, class: 'xlarge', signup: true, quick: true, info: true, invite_agent: true },
{ name: 'web', display: 'Web', tag: 'input', type: 'url', limit: 100, null: true, class: 'xlarge', signup: false, quick: true, info: true },
{ name: 'phone', display: 'Phone', tag: 'input', type: 'phone', limit: 100, null: true, class: 'xlarge', signup: false, quick: true, info: true },
{ name: 'mobile', display: 'Mobile', tag: 'input', type: 'phone', limit: 100, null: true, class: 'xlarge', signup: false, quick: true, info: true },
{ name: 'fax', display: 'Fax', tag: 'input', type: 'phone', limit: 100, null: true, class: 'xlarge', signup: false, quick: true, info: true },
{ name: 'password', display: 'Password', tag: 'input', type: 'password', limit: 50, null: true, class: 'xlarge', signup: true, quick: false, },
{ name: 'organization_id', display: 'Organization', tag: 'select', multiple: false, nulloption: true, null: true, relation: 'Organization', class: 'xlarge' },
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true, class: 'xlarge', quick: true, info: true },
{ name: 'role_ids', display: 'Roles', tag: 'checkbox', multiple: true, null: false, relation: 'Role', class: 'xlarge' },
{ name: 'group_ids', display: 'Groups', tag: 'checkbox', multiple: true, null: true, relation: 'Group', class: 'xlarge', invite_agent: true },
{ name: 'active', display: 'Active', tag: 'boolean', default: true, null: true, class: 'xlarge' },
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 },
]
@configure_overview = [
# 'login', 'firstname', 'lastname', 'email', 'updated_at',
'login', 'firstname', 'lastname'
]

View file

@ -0,0 +1,27 @@
<div class="page-header">
<h1>New Ticket <small></small></h1>
</div>
<form class="form-horizontal">
<div class="row">
<div class="span9">
<%- @form %>
<!--
<legend>Example form legend</legend>
-->
<!--
<div class="clearfix">
<label for="fileInput">File input</label>
<div class="input">
<input class="input-file" id="fileInput" name="fileInput" type="file" />
</div>
</div><!-- /clearfix -->
</div>
<div class="span3">
<div class="span3" id="customer_info"></div>
</div>
</div>
<div class="form-actions">
<button type="reset" class="btn">Cancel</button>&nbsp;<input type="submit" class="btn-primary" value="Create"/>
</div>
</form>

View file

@ -0,0 +1,10 @@
<div class="modal-header">
<a href="#" class="close">&times;</a>
<h3>History</h3>
</div>
<div class="modal-body">
<form class="form-horizontal"></form>
<div class="table_history"></div>
</div>
<div class="modal-footer">
</div>

Some files were not shown because too many files have changed in this diff Show more