Added text module feature.

This commit is contained in:
Martin Edenhofer 2012-10-22 15:00:26 +02:00
parent e4506dd1a3
commit 898ee67a88
22 changed files with 590 additions and 67 deletions

View file

@ -12,23 +12,21 @@ class App.ControllerGenericNew extends App.ControllerModal
render: ->
@html App.view('generic/admin/new')( head: @pageData.object )
new App.ControllerForm(
el: @el.find('#object_new'),
model: @genericObject,
model: App[ @genericObject ],
params: @item,
required: @required,
autofocus: true,
)
@modalShow()
submit: (e) ->
@log 'submit'
e.preventDefault()
params = @formParam(e.target)
params = @formParam( e.target )
object = new @genericObject
object = new App[ @genericObject ]
object.load(params)
# validate
@ -53,7 +51,7 @@ class App.ControllerGenericNew extends App.ControllerModal
ui.modalHide()
App.Collection.find( ui.pageData.object, @id, callbackReload , true )
error: =>
error: ->
ui.log 'errors'
ui.modalHide()
)
@ -64,28 +62,27 @@ class App.ControllerGenericEdit extends App.ControllerModal
@log 'ControllerGenericEditWindow', params
# fetch item on demand
if @genericObject.exists(params.id)
@item = @genericObject.find(params.id)
if App[ @genericObject ].exists( params.id )
@item = App[ @genericObject ].find( params.id )
@render()
else
@genericObject.bind 'refresh', =>
App[ @genericObject ].bind 'refresh', =>
@log 'changed....'
@item = @genericObject.find(params.id)
@item = App[ @genericObject ].find( params.id )
@render()
@genericObject.unbind 'refresh'
@genericObject.fetch( id: params.id)
App[ @genericObject ].unbind 'refresh'
App[ @genericObject ].fetch( id: params.id )
render: ->
@html App.view('generic/admin/edit')( head: @pageData.object )
@html App.view('generic/admin/edit')( head: @pageData.object )
new App.ControllerForm(
el: @el.find('#object_edit'),
model: @genericObject,
model: App[ @genericObject ],
params: @item,
required: @required,
autofocus: true,
)
@modalShow()
submit: (e) ->
@ -139,8 +136,8 @@ class App.ControllerGenericIndex extends App.Controller
@navupdate @pageData.navupdate
# bind render after a change is done
@genericObject.bind 'refresh change', @render
@genericObject.bind 'ajaxError', (rec, msg) =>
App[ @genericObject ].bind 'refresh change', @render
App[ @genericObject ].bind 'ajaxError', (rec, msg) =>
@log 'ajax notice', msg.status
if msg.status is 401
@log 'ajax error', rec, msg, msg.status
@ -149,16 +146,16 @@ class App.ControllerGenericIndex extends App.Controller
@navigate 'login'
# execute fetch, if needed
if !@genericObject.count() || true
# if !@genericObject.count()
if !App[ @genericObject ].count() || true
# if !App[ @genericObject ].count()
# prerender without content
@render()
# fetch all
# @log 'oooo', @genericObject.model
# @genericObject.deleteAll()
@genericObject.fetch()
# @log 'oooo', App[ @genericObject ].model
# App[ @genericObject ].deleteAll()
App[ @genericObject ].fetch()
else
@render()
@ -166,7 +163,10 @@ class App.ControllerGenericIndex extends App.Controller
return if Config['ActiveController'] isnt @pageData.navupdate
objects = @genericObject.all()
objects = App.Collection.all(
type: @genericObject,
sortBy: @defaultSortBy || 'name',
)
# remove ignored items from collection
if @ignoreObjectIDs
@ -185,21 +185,21 @@ class App.ControllerGenericIndex extends App.Controller
# append content table
new App.ControllerTable(
el: @el.find('.table-overview'),
model: @genericObject,
model: App[ @genericObject ],
objects: objects,
)
edit: (e) =>
e.preventDefault()
item = $(e.target).item(@genericObject)
item = $(e.target).item( App[ @genericObject ] )
new App.ControllerGenericEdit(
id: item.id,
pageData: @pageData,
id: item.id,
pageData: @pageData,
genericObject: @genericObject
)
destroy: (e) ->
item = $(e.target).item(@genericObject)
item = $(e.target).item( App[ @genericObject ] )
item.destroy() if confirm('Sure?')
new: (e) ->

View file

@ -138,6 +138,11 @@ class Index extends App.Controller
template_id: template['id'],
)
# show text module UI
new App.TextModuleUI(
el: @el.find('#text_module'),
)
localUserInfo: (params) =>
@userInfo( user_id: params.customer_id )

View file

@ -39,7 +39,7 @@ class App.TicketHistory extends App.ControllerModal
render: ->
@html App.view('agent_ticket_history')(
objects: App.Collection.all( 'History' ),
objects: App.Collection.all( type: 'History' ),
)
@modalShow()

View file

@ -203,6 +203,12 @@ class Index extends App.Controller
object: @ticket,
)
# show text module UI
if !@isRole('Customer')
new App.TextModuleUI(
el: @el.find('#text_module'),
)
show_toogle: (e) ->
e.preventDefault()
$(e.target).hide()
@ -320,12 +326,12 @@ class Index extends App.Controller
# @log 'reply ', article, @el.find('[name="to"]')
# add quoted text if needed
if window.Session['UISelection']
selectedText = App.ClipBoard.getSelected()
if selectedText
body = @el.find('[name="body"]').val() || ''
selection = window.Session['UISelection'].trim()
selection = selection.replace /^(.*)$/mg, (match) =>
selectedText = selectedText.replace /^(.*)$/mg, (match) =>
'> ' + match
body = selection + "\n" + body
body = selectedText + "\n" + body
@el.find('[name="body"]').val(body)
# update textarea size

View file

@ -10,7 +10,7 @@ class Index extends App.Controller
new App.ControllerGenericIndex(
el: @el,
id: @id,
genericObject: App.Group,
genericObject: 'Group',
pageData: {
title: 'Groups',
home: 'groups',

View file

@ -10,7 +10,7 @@ class Index extends App.Controller
new App.ControllerGenericIndex(
el: @el,
id: @id,
genericObject: App.Organization,
genericObject: 'Organization',
pageData: {
title: 'Organizations',
home: 'organizations',

View file

@ -0,0 +1,31 @@
$ = jQuery.sub()
class Index extends App.Controller
constructor: ->
super
# check authentication
return if !@authenticate()
new App.ControllerGenericIndex(
el: @el,
id: @id,
genericObject: 'TextModule',
pageData: {
title: 'TextModules',
home: 'text_modules',
object: 'TextModule',
objects: 'TextModules',
navupdate: '#text_modules',
notes: [
'TextModules are ...'
],
buttons: [
{ name: 'New TextModule', 'data-type': 'new', class: 'primary' },
],
},
)
Config.Routes['text_modules'] = Index
Config.NavBar['AdminTextModule'] = { prio: 2300, parent: '#admin', name: 'Text Modules', target: '#text_modules', role: ['Admin'] }

View file

@ -0,0 +1,212 @@
$ = jQuery.sub()
class App.TextModuleUI extends App.Controller
events:
'click [data-type=text_module_save]': 'create',
'click [data-type=text_module_select]': 'select',
'click [data-type=text_module_delete]': 'delete',
'click [data-type=edit]': 'select',
'dblclick [data-type=edit]': 'paste',
constructor: ->
super
# fetch item on demand
fetch_needed = 1
if App.Collection.count( 'TextModule' ) > 0
fetch_needed = 0
@render()
if fetch_needed
@reload()
reload: =>
App.TextModule.bind 'refresh', =>
@log 'loading....'
@render()
App.TextModule.unbind 'refresh'
App.Collection.fetch( 'TextModule' )
render: =>
ui = @
a = $('textarea')
# remember active text element
a.bind('focusin', ->
ui.area = $(@)
)
ui.C = false
ui.CList = ''
a.bind('keydown', (e) ->
# lisen if crtl is pressed
if ui.C
key = App.ClipBoard.keycode( e.keyCode )
# remove one char
if key is 'backspace'
ui.CList = ui.CList.slice( 0, -1 )
# take over
else if key is 'enter'
# ui.CList = ui.CList.slice(0, -1)
objects = ui.objectSearch( ui.CList )
if objects[0]
ui._insert( objects[0].content, ui )
# reset search
ui.CList = ''
ui.renderTable()
# add char to search selection
else
ui.CList = ui.CList + key
console.log 'CTRL+', ui.CList
ui.el.find('#text-module-search').val( ui.CList )
ui.renderTable( ui.CList )
# start current search process
if e.ctrlKey
ui.C = true
)
# start current search process
# do code to test other keys
a.bind('keyup', (e) ->
if e.keyCode == 17
console.log 'CTRL UP - pressed ', ui.CList
ui.CList = ''
ui.C = false
ui.renderTable()
)
@configure_attributes = [
{ name: 'text_module_id', display: '', tag: 'select', multiple: false, null: true, nulloption: true, relation: 'TextModule', class: 'span2', default: @text_module_id },
]
text_module = {}
if @text_module_id
text_module = App.Collection.find( 'TextModule', @text_module_id )
# insert data
@html App.view('text_module')(
text_module: text_module,
search: @search,
)
# rerender if search phrase has changed
@el.find('#text-module-search').unbind('keyup').bind('keyup', =>
search = $('#text-module-search').val();
console.log 'SEARCH', search
@renderTable( search )
)
@renderTable('')
objectSearch: (search) =>
objects = App.Collection.all(
type: 'TextModule',
sortBy: 'name',
filter: { active: true },
filterExtended: [ { name: search }, { content: search }, { keywords: search } ],
)
renderTable: (search) =>
objects = @objectSearch(search)
@el.find('#form-text-module').html('')
new App.ControllerTable(
el: @el.find('#form-text-module'),
# header: ['Name'],
overview: ['name'],
model: App.TextModule,
objects: objects,
# radio: true,
)
paste: (e) =>
e.preventDefault()
id = $(e.target).parents('tr').data('id')
text_module = App.Collection.find( 'TextModule', id )
@_insert( text_module.content, @ )
delete: (e) =>
e.preventDefault()
# get params
params = @formParam(e.target)
text_module = App.Collection.find( 'TextModule', params['text_module_id'] )
if confirm('Sure?')
text_module.destroy()
@text_module_id = undefined
@render()
select: (e) =>
e.preventDefault()
id = $(e.target).parents('tr').data('id')
text_module = App.Collection.find( 'TextModule', id )
@el.find('#text-module-preview-content').val( text_module.content )
create: (e) =>
e.preventDefault()
# get params
params = @formParam(e.target)
name = params['text_module_name']
# delete params['text_module_name']
text_module = App.Collection.findByAttribute( 'TextModule', 'name', name )
if !text_module
text_module = new App.TextModule
content = App.ClipBoard.getSelectedLast()
if content
text_module.load(
name: params['text_module_name']
content: content
)
# validate form
errors = text_module.validate()
# show errors in form
if errors
@log 'error new', errors
else
ui = @
text_module.save(
success: ->
ui.text_module_id = @.id
ui.render()
ui.log 'save success!'
error: ->
ui.log 'save failed!'
)
_insert: (contentNew, ui) ->
position = ui.area.prop('selectionStart')
content = ui.area.val()
start = content.substr( 0, position )
end = content.substr( position, content.length )
# check if \n is needed
startEnd = start.substr( start.length-2, 2 )
if position is 0 || startEnd is "\n\n"
startDiver = ''
else
startDiver = "\n"
content = start + startDiver + contentNew + end
ui.area.val(content)
# update cursor position
currentPosition = (position + contentNew.length + startDiver.length )
ui.area.prop('selectionStart', currentPosition )
ui.area.prop('selectionEnd', currentPosition )

View file

@ -10,7 +10,8 @@ class Index extends App.Controller
new App.ControllerGenericIndex(
el: @el,
id: @id,
genericObject: App.User,
genericObject: 'User',
defaultSortBy: 'login',
ignoreObjectIDs: [1],
pageData: {
title: 'Users',

View file

@ -24,6 +24,7 @@
#not_used= require_tree ./lib
#= require_self
#= require ./lib/ajax.js.coffee
#= require ./lib/clipboard.js.coffee
#= require ./lib/websocket.js.coffee
#= require ./lib/auth.js.coffee
#= require ./lib/i18n.js.coffee

View file

@ -202,14 +202,24 @@ class _Singleton
get: (params) ->
console.log('get')
App[params.type].refresh( object, options: { clear: true } )
App[ params.type ].refresh( object, options: { clear: true } )
all: (type) ->
all = App[type].all()
all: (params) ->
all = App[ params.type ].all()
all_complied = []
for item in all
item_new = @find( type, item.id )
item_new = @find( params.type, item.id )
all_complied.push item_new
if params.filter
all_complied = @_filter( all_complied, params.filter )
if params.filterExtended
all_complied = @_filterExtended( all_complied, params.filterExtended )
if params.sortBy
all_complied = @_sortBy( all_complied, params.sortBy )
return all_complied
deleteAll: (type) ->
@ -223,3 +233,40 @@ class _Singleton
fetch: ( type ) ->
App[type].fetch()
_sortBy: ( collection, attribute ) ->
_.sortBy( collection, (item) -> return item[ attribute ].toLowerCase() )
_filter: ( collection, filter ) ->
for key, value of filter
collection = _.filter( collection, (item) ->
if item[ key ] is value
return item
)
return collection
_filterExtended: ( collection, filters ) ->
collection = _.filter( collection, (item) ->
# check all filters
for filter in filters
# all conditions need match
matchInner = undefined
for key, value of filter
if matchInner isnt false
reg = new RegExp( value, 'i' )
if item[ key ] isnt undefined && item[ key ] isnt null && item[ key ].match( reg )
matchInner = true
else
matchInner = false
# if all matched, add item to new collection
if matchInner is true
return item
return
)
return collection

View file

@ -27,19 +27,7 @@ class App.Run extends App.Controller
new App.Content( el: @el.find('#content') )
# bind to fill selected text into
$(@el).bind('mouseup', =>
window.Session['UISelection'] = @getSelected() + ''
)
getSelected: ->
text = '';
if window.getSelection
text = window.getSelection()
else if document.getSelection
text = document.getSelection()
else if document.selection
text = document.selection.createRange().text
text
App.ClipBoard.bind( @el )
class App.Content extends Spine.Controller
className: 'container'

View file

@ -29,7 +29,7 @@ class _Singleton extends Spine.Controller
send: (data) =>
return if !@supported
console.log 'ws:send trying', data, @ws, @ws.readyState
# console.log 'ws:send trying', data, @ws, @ws.readyState
# A value of 0 indicates that the connection has not yet been established.
# A value of 1 indicates that the connection is established and communication is possible.
@ -38,7 +38,7 @@ class _Singleton extends Spine.Controller
if @ws.readyState is 0
@queue.push data
else
console.log( 'ws:send', data )
# console.log( 'ws:send', data )
string = JSON.stringify( data )
@ws.send(string)
@ -60,7 +60,7 @@ class _Singleton extends Spine.Controller
ping: =>
return if !@supported
console.log 'send websockend ping'
# console.log 'send websockend ping'
@send( { action: 'ping' } )
# check if ping is back within 2 min
@ -73,8 +73,7 @@ class _Singleton extends Spine.Controller
pong: ->
return if !@supported
console.log 'received websockend ping'
# console.log 'received websockend ping'
# test again after 1 min
@delay @ping, 60000
@ -111,7 +110,7 @@ class _Singleton extends Spine.Controller
# empty queue
for item in @queue
console.log( 'ws:send queue', item )
# console.log( 'ws:send queue', item )
@send(item)
@queue = []

View file

@ -0,0 +1,16 @@
class App.TextModule extends App.Model
@configure 'TextModule', 'name', 'keywords', 'content', 'active', 'group_ids', 'user_id'
@extend Spine.Model.Ajax
@url: '/api/text_modules'
@configure_attributes = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, 'class': 'span4' },
{ name: 'keywords', display: 'Keywords', tag: 'input', type: 'text', limit: 100, 'null': true, 'class': 'span4' },
{ name: 'content', display: 'Content', tag: 'textarea', limit: 250, 'null': false, 'class': 'span4' },
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 },
{ name: 'active', display: 'Active', tag: 'boolean', note: 'boolean', 'default': true, 'null': false, 'class': 'span4' },
]
@configure_overview = [
'name',
'keywords',
'content',
]

View file

@ -7,6 +7,7 @@
<div class="span3">
<div id="customer_info"></div>
<div id="ticket_template"></div>
<div id="text_module"></div>
</div>
</div>
<div class="form-actions">

View file

@ -91,9 +91,10 @@
</div>
<div class="span3">
<div class="row" id="customer_info"></div>
<div class="row" id="action_info"></div>
<div class="row" id="link_info"></div>
<div id="customer_info"></div>
<div id="action_info"></div>
<div id="link_info"></div>
<div id="text_module"></div>
</div>
</div>

View file

@ -0,0 +1,20 @@
<div class="well">
<h3><%- @T( 'Text Modules' ) %></h3>
<%- @T( 'Search' ) %>
<br>
<input type="text" name="" id="text-module-search" class="span2" value="<%= @search %>" autocomplete="off"/>
<br>
<div id="form-text-module"></div>
<!--<button type="submit" class="btn" data-type="text_module_edit"><%- @T( 'Edit' ) %></button>-->
<!--
<button type="submit" class="btn" data-type="text_module_delete"><%- @T( 'Delete' ) %></button>
<button type="submit" class="btn" data-type="text_module_select"><%- @T( 'Apply' ) %></button>
-->
<%- @T( 'Preview ') %>
<br>
<textarea id="text-module-preview-content" class="span2"></textarea>
<hr>
<label class="" for="text_module_name"><%- @T( 'Save as Text Module' ) %></label>
<input type="text" name="text_module_name" id="text_module_name" class="span2" value="<%= @text_module.name %>"/>
<button type="submit" class="btn" data-type="text_module_save"><%- @T( 'Save' ) %></button>
</div>

View file

@ -0,0 +1,148 @@
class TextModulesController < ApplicationController
before_filter :authentication_check
=begin
Format:
JSON
Example:
{
"id":1,
"name":"some text_module",
"user_id": null,
"keywords":"some keywords",
"content":"some content",
"active":true,
"updated_at":"2012-09-14T17:51:53Z",
"created_at":"2012-09-14T17:51:53Z",
"updated_by_id":2.
"created_by_id":2,
}
=end
=begin
Resource:
GET /api/text_modules.json
Response:
[
{
"id": 1,
"name": "some_name1",
...
},
{
"id": 2,
"name": "some_name2",
...
}
]
Test:
curl http://localhost/api/text_modules.json -v -u #{login}:#{password}
=end
def index
model_index_render(TextModule, params)
end
=begin
Resource:
GET /api/text_modules/#{id}.json
Response:
{
"id": 1,
"name": "name_1",
...
}
Test:
curl http://localhost/api/text_modules/#{id}.json -v -u #{login}:#{password}
=end
def show
model_show_render(TextModule, params)
end
=begin
Resource:
POST /api/text_modules.json
Payload:
{
"name": "some name",
"keywords":"some keywords",
"content":"some content",
"active":true,
}
Response:
{
"id": 1,
"name": "some_name",
...
}
Test:
curl http://localhost/api/text_modules.json -v -u #{login}:#{password} -H "Content-Type: application/json" -X POST -d '{"name": "some_name","active": true, "note": "some note"}'
=end
def create
model_create_render(TextModule, params)
end
=begin
Resource:
PUT /api/text_modules/{id}.json
Payload:
{
"name": "some name",
"keywords":"some keywords",
"content":"some content",
"active":true,
}
Response:
{
"id": 1,
"name": "some_name",
...
}
Test:
curl http://localhost/api/text_modules.json -v -u #{login}:#{password} -H "Content-Type: application/json" -X PUT -d '{"name": "some_name","active": true, "note": "some note"}'
=end
def update
model_update_render(TextModule, params)
end
=begin
Resource:
DELETE /api/text_modules/{id}.json
Response:
{}
Test:
curl http://localhost/api/text_modules.json -v -u #{login}:#{password} -H "Content-Type: application/json" -X DELETE
=end
def destroy
model_destory_render(TextModule, params)
end
end

View file

@ -0,0 +1,4 @@
class TextModule < ApplicationModel
validates :name, :presence => true
validates :content, :presence => true
end

View file

@ -0,0 +1,13 @@
module ExtraRoutes
def add(map)
# roles
map.match '/api/text_modules', :to => 'text_modules#index', :via => :get
map.match '/api/text_modules/:id', :to => 'text_modules#show', :via => :get
map.match '/api/text_modules', :to => 'text_modules#create', :via => :post
map.match '/api/text_modules/:id', :to => 'text_modules#update', :via => :put
map.match '/api/text_modules/:id', :to => 'templates#destroy', :via => :delete
end
module_function :add
end

View file

@ -0,0 +1,30 @@
class TextModuleCreate < ActiveRecord::Migration
def up
create_table :text_modules do |t|
t.references :user, :null => true
t.column :name, :string, :limit => 250, :null => false
t.column :keywords, :string, :limit => 500, :null => true
t.column :content, :string, :limit => 5000, :null => false
t.column :note, :string, :limit => 250, :null => true
t.column :active, :boolean, :null => false, :default => true
t.column :updated_by_id, :integer, :null => false
t.column :created_by_id, :integer, :null => false
t.timestamps
end
add_index :text_modules, [:user_id]
add_index :text_modules, [:name]
create_table :text_modules_groups, :id => false do |t|
t.integer :text_module_id
t.integer :group_id
end
add_index :text_modules_groups, [:text_module_id]
add_index :text_modules_groups, [:group_id]
end
def down
drop_table :text_modules_groups
drop_table :text_modules
end
end