2011-09-08 01:32:24 +02:00
/ * *
* eGroupWare eTemplate2 - JS Link object
*
* @ license http : //opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @ package etemplate
* @ subpackage api
* @ link http : //www.egroupware.org
* @ author Nathan Gray
* @ copyright 2011 Nathan Gray
* @ version $Id$
* /
"use strict" ;
/ * e g w : u s e s
jquery . jquery ;
jquery . jquery - ui ;
et2 _core _inputWidget ;
et2 _core _valueWidget ;
2012-06-19 20:59:53 +02:00
// Include menu system for list context menu
egw _action . egw _menu _dhtmlx ;
2011-09-08 01:32:24 +02:00
* /
/ * *
* UI widgets for Egroupware linking system
* /
var et2 _link _to = et2 _inputWidget . extend ( {
attributes : {
"application" : {
"name" : "Application" ,
"type" : "string" ,
2011-09-14 22:36:39 +02:00
"default" : "" ,
2011-09-08 01:32:24 +02:00
"description" : "Limit to the listed application or applications (comma seperated)"
} ,
"blur" : {
"name" : "Placeholder" ,
"type" : "string" ,
"default" : "" ,
"description" : "This text get displayed if an input-field is empty and does not have the input-focus (blur). It can be used to show a default value or a kind of help-text."
} ,
2011-09-14 02:06:04 +02:00
"no_files" : {
"name" : "No files" ,
"type" : "boolean" ,
"default" : false ,
"description" : "Suppress attach-files"
} ,
"search_label" : {
"name" : "Search label" ,
"type" : "string" ,
"default" : "" ,
"description" : "Label to use for search"
} ,
"link_label" : {
"name" : "Link label" ,
"type" : "string" ,
"default" : "Link" ,
"description" : "Label for the link button"
}
2011-09-08 01:32:24 +02:00
} ,
search _timeout : 200 , //ms after change to send query
minimum _characters : 2 , // Don't send query unless there's at least this many chars
init : function ( ) {
this . _super . apply ( this , arguments ) ;
this . div = null ;
2011-09-13 01:43:39 +02:00
2011-09-09 02:05:46 +02:00
this . link _button = null ;
this . status _span = null ;
this . comment = null ;
2011-09-08 01:32:24 +02:00
2011-09-13 01:43:39 +02:00
this . link _entry = null ;
this . file _upload = null ;
2011-09-08 01:32:24 +02:00
this . createInputWidget ( ) ;
} ,
destroy : function ( ) {
2011-09-09 02:05:46 +02:00
this . link _button = null ;
this . status _span = null ;
this . comment = null ;
2011-09-13 01:43:39 +02:00
this . link _entry . destroy ( ) ;
this . link _entry = null ;
2011-09-09 02:05:46 +02:00
this . file _upload . destroy ( ) ;
this . file _upload = null ;
2011-09-13 01:43:39 +02:00
this . div = null ;
2012-03-01 11:13:12 +01:00
this . _super . apply ( this , arguments ) ;
2011-09-09 02:05:46 +02:00
} ,
2011-09-08 01:32:24 +02:00
2011-09-09 02:05:46 +02:00
/ * *
2011-09-13 01:43:39 +02:00
* Override to provide proper node for sub widgets to go in
2011-09-09 02:05:46 +02:00
* /
getDOMNode : function ( _sender ) {
if ( _sender == this ) {
return this . div [ 0 ] ;
2011-09-13 01:43:39 +02:00
} else if ( _sender . _type == 'link-entry' ) {
return this . link _div [ 0 ] ;
} else if ( _sender . _type == 'file' ) {
2011-09-09 02:05:46 +02:00
return this . file _div [ 0 ] ;
2011-09-13 01:43:39 +02:00
2011-09-09 02:05:46 +02:00
}
2011-09-08 01:32:24 +02:00
} ,
createInputWidget : function ( ) {
2011-09-09 02:05:46 +02:00
this . div = $j ( document . createElement ( "div" ) ) . addClass ( "et2_link_to" ) ;
2011-09-08 01:32:24 +02:00
2011-09-13 01:43:39 +02:00
// One common link button
this . link _button = $j ( document . createElement ( "button" ) )
2012-03-02 11:44:56 +01:00
. text ( this . egw ( ) . lang ( this . options . link _label ) )
2011-09-13 01:43:39 +02:00
. appendTo ( this . div ) . hide ( )
. click ( this , this . createLink ) ;
// Span for indicating status
this . status _span = $j ( document . createElement ( "span" ) )
. appendTo ( this . div ) . addClass ( "status" ) . hide ( ) ;
// Need a div for link-to widget
2012-04-06 00:30:06 +02:00
this . link _div = $j ( document . createElement ( "div" ) ) . css ( "margin-bottom" , "1ex" ) . appendTo ( this . div ) ;
2011-09-13 01:43:39 +02:00
// Link comment field
this . comment = $j ( document . createElement ( "input" ) )
. css ( "display" , "block" ) . css ( "width" , "89%" )
. appendTo ( this . div ) . hide ( ) ;
2012-03-02 11:44:56 +01:00
et2 _link _entry . prototype . set _blur ( this . egw ( ) . lang ( "Comment..." ) , this . comment ) ;
2011-09-13 01:43:39 +02:00
2012-06-27 01:01:04 +02:00
// Filemanager link popup
2012-07-03 01:03:27 +02:00
var self = this ;
2012-06-27 01:01:04 +02:00
this . filemanager _button = $j ( document . createElement ( "img" ) )
. attr ( "src" , this . egw ( ) . image ( "filemanager/navbar" ) )
. addClass ( "et2_button et2_button_icon" )
. appendTo ( this . div )
. click ( this , function ( e ) {
// Open the filemanager select in a popup
var values = e . data . options . value ;
2012-07-03 01:03:27 +02:00
var popup = e . data . egw ( ) . open _link (
2012-06-27 01:01:04 +02:00
'/index.php?menuaction=filemanager.filemanager_select.select&mode=open-multiple&method=etemplate_widget_link::link_existing&label=link&id=' + values . to _app + ":" + values . to _id ,
2012-07-03 01:03:27 +02:00
'link_existing' ,
2012-06-27 01:01:04 +02:00
'640x580'
) ;
2012-07-03 01:03:27 +02:00
if ( popup )
{
// Update on close doesn't always (ever, in chrome) work, so poll
var poll = self . egw ( ) . window . setInterval (
function ( ) {
if ( popup . closed ) {
self . getRoot ( ) . iterateOver (
function ( widget ) {
if ( widget . id == self . id ) {
widget . _get _links ( ) ;
}
} ,
self , et2 _link _list
)
self . egw ( ) . window . clearInterval ( poll ) ;
}
} , 1000 ) ;
}
2012-06-27 01:01:04 +02:00
} ) ;
2011-09-13 01:43:39 +02:00
// Need a div for file upload widget
this . file _div = $j ( document . createElement ( "div" ) ) . appendTo ( this . div ) ;
this . setDOMNode ( this . div [ 0 ] ) ;
} ,
doLoadingFinished : function ( ) {
this . _super . apply ( this , arguments ) ;
var self = this ;
// Link-to
var link _entry _attrs = {
id : this . id + '_link_entry' ,
2012-03-02 11:44:56 +01:00
blur : this . options . search _label ? this . options . search _label : this . egw ( ) . lang ( 'Search...' ) ,
2011-09-13 01:43:39 +02:00
query : function ( ) { self . link _button . hide ( ) ; self . comment . hide ( ) ; return true ; } ,
select : function ( ) { self . link _button . show ( ) ; self . comment . show ( ) ; return true ; }
}
this . link _entry = et2 _createWidget ( "link-entry" , link _entry _attrs , this ) ;
// File upload
var file _attrs = {
multiple : true ,
id : this . id + '_file' ,
onFinish : function ( event , file _count ) {
2012-04-24 18:33:56 +02:00
event . data = self ;
2011-09-13 01:43:39 +02:00
self . filesUploaded ( event ) ;
2012-04-24 18:33:56 +02:00
// Auto-link uploaded files
if ( self . options . value . to _id ) self . createLink ( event ) ;
2011-09-13 01:43:39 +02:00
}
} ;
this . file _upload = et2 _createWidget ( "file" , file _attrs , this ) ;
return true ;
} ,
getValue : function ( ) {
return this . options . value ;
} ,
filesUploaded : function ( event ) {
var self = this ;
this . link _button . show ( ) ;
} ,
/ * *
* Create a link using the current internal values
* /
createLink : function ( event ) {
// Disable link button
event . data . link _button . attr ( "disabled" , true ) ;
var values = event . data . options . value ;
var self = event . data ;
var links = [ ] ;
// Links to other entries
event . data = self . link _entry ;
self . link _entry . createLink ( event , links ) ;
// Add comment
2011-09-14 02:06:04 +02:00
if ( links . length > 0 && self . comment . val ( ) && self . comment . val ( ) != self . comment . attr ( "placeholder" ) )
2011-09-13 01:43:39 +02:00
{
for ( var i = 0 ; i < links . length ; i ++ )
{
links [ i ] . remark = self . comment . val ( ) ;
}
2011-09-14 02:06:04 +02:00
self . comment . val ( self . comment . attr ( "placeholder" ) ) ;
2011-09-13 01:43:39 +02:00
}
// Files
for ( var file in self . file _upload . options . value ) {
links . push ( {
app : 'file' ,
id : file ,
name : self . file _upload . options . value [ file ] . name ,
type : self . file _upload . options . value [ file ] . type ,
remark : jQuery ( "li[file='" + self . file _upload . options . value [ file ] . name + "'] > input" , self . file _upload . progress )
. filter ( function ( ) { return jQuery ( this ) . attr ( "placeholder" ) != jQuery ( this ) . val ( ) ; } ) . val ( )
} ) ;
}
var request = new egw _json _request ( "etemplate_widget_link::ajax_link::etemplate" ,
[ values . to _app , values . to _id , links ] ,
this
) ;
request . sendRequest ( true , self . _link _result , self ) ;
} ,
/ * *
* Sent some links , server has a result
* /
_link _result : function ( success ) {
if ( success ) {
this . comment . hide ( ) ;
this . link _button . hide ( ) . attr ( "disabled" , false ) ;
this . status _span . fadeIn ( ) . delay ( 1000 ) . fadeOut ( ) ;
delete this . options . value . app ;
delete this . options . value . id ;
for ( var file in this . file _upload . options . value ) {
delete this . file _upload . options . value [ file ] ;
}
this . file _upload . progress . empty ( ) ;
2011-09-14 02:06:04 +02:00
// Look for a link-list with the same ID, refresh it
var self = this ;
this . getRoot ( ) . iterateOver (
function ( widget ) {
if ( widget . id == self . id ) {
widget . _get _links ( ) ;
}
} ,
this , et2 _link _list
) ;
2011-09-13 01:43:39 +02:00
}
}
} ) ;
et2 _register _widget ( et2 _link _to , [ "link-to" ] ) ;
2011-09-14 22:36:39 +02:00
var et2 _link _apps = et2 _selectbox . extend ( {
attributes : {
"application" : {
"name" : "Application" ,
"type" : "string" ,
"default" : "" ,
"description" : "Limit to the listed application or applications (comma seperated)"
}
} ,
init : function ( ) {
this . _super . apply ( this , arguments ) ;
var select _options = { } ;
// Limit to one app
if ( this . options . application ) {
2012-03-02 11:44:56 +01:00
select _options [ _attrs . application ] = this . egw ( ) . lang ( _attrs . application ) ;
2011-09-14 22:36:39 +02:00
} else {
2012-03-02 11:44:56 +01:00
select _options = this . egw ( ) . link _app _list ( 'query' ) ;
2011-09-14 22:36:39 +02:00
// Check whether the options entry was found, if not read it from the
// content array.
if ( select _options == null )
{
select _options = this . getArrayMgr ( 'content' )
. getEntry ( "options-" + this . id )
}
// Default to an empty object
if ( select _options == null )
{
select _options = { } ;
}
}
this . set _select _options ( select _options ) ;
}
} ) ;
et2 _register _widget ( et2 _link _apps , [ "link-apps" ] ) ;
2012-03-23 00:20:56 +01:00
var et2 _link _entry = et2 _inputWidget . extend ( {
2011-09-13 01:43:39 +02:00
attributes : {
2012-06-20 01:30:07 +02:00
"value" : {
"type" : "any" ,
"default" : { }
} ,
2011-09-13 01:43:39 +02:00
"application" : {
"name" : "Application" ,
"type" : "string" ,
2011-09-14 22:36:39 +02:00
"default" : "" ,
2011-09-13 01:43:39 +02:00
"description" : "Limit to the listed application or applications (comma seperated)"
} ,
"blur" : {
"name" : "Placeholder" ,
"type" : "string" ,
2012-04-09 20:56:04 +02:00
"default" : et2 _no _init ,
2011-09-13 01:43:39 +02:00
"description" : "This text get displayed if an input-field is empty and does not have the input-focus (blur). It can be used to show a default value or a kind of help-text."
} ,
"query" : {
"name" : "Query callback" ,
"type" : "any" ,
"default" : false ,
"description" : "Callback before query to server. Must return true, or false to abort query."
} ,
"select" : {
"name" : "Select callback" ,
"type" : "any" ,
"default" : false ,
"description" : "Callback when user selects an option. Must return true, or false to abort normal action."
} ,
} ,
2012-04-04 22:17:38 +02:00
legacyOptions : [ "application" ] ,
2011-09-13 01:43:39 +02:00
search _timeout : 200 , //ms after change to send query
minimum _characters : 2 , // Don't send query unless there's at least this many chars
init : function ( ) {
this . _super . apply ( this , arguments ) ;
this . div = null ;
this . search = null ;
this . app _select = null ;
2012-06-26 01:05:18 +02:00
this . _oldValue = { id : null , app : this . options . application } ;
2011-09-13 01:43:39 +02:00
if ( typeof this . options . value == 'undefined' ) this . options . value = { } ;
this . cache = { } ;
this . createInputWidget ( ) ;
} ,
destroy : function ( ) {
this . _super . apply ( this , arguments ) ;
this . div = null ;
this . search . autocomplete ( "destroy" ) ;
this . search = null ;
this . app _select = null ;
} ,
createInputWidget : function ( ) {
var self = this ;
this . div = $j ( document . createElement ( "div" ) ) . addClass ( "et2_link_entry" ) ;
2011-09-08 01:32:24 +02:00
// Application selection
2011-10-18 21:15:32 +02:00
2011-09-08 01:32:24 +02:00
this . app _select = $j ( document . createElement ( "select" ) ) . appendTo ( this . div )
. change ( function ( e ) {
self . cache = { } ; // Clear cache when app changes
2012-06-11 18:35:46 +02:00
if ( typeof self . options . value != 'object' ) self . options . value = { } ;
2011-09-09 02:05:46 +02:00
self . options . value . app = self . app _select . val ( ) ;
2012-07-11 21:01:06 +02:00
} ) ;
2011-10-18 21:15:32 +02:00
var opt _count = 0 ;
2011-09-08 01:32:24 +02:00
for ( var key in this . options . select _options ) {
2011-10-18 21:15:32 +02:00
opt _count ++ ;
2011-09-08 01:32:24 +02:00
var option = $j ( document . createElement ( "option" ) )
. attr ( "value" , key )
. text ( this . options . select _options [ key ] ) ;
option . appendTo ( this . app _select ) ;
}
2011-10-18 21:15:32 +02:00
this . app _select . val ( this . options . application ) ;
if ( opt _count == 1 )
{
this . app _select . hide ( ) ;
2012-04-24 23:27:48 +02:00
this . div . addClass ( "no_app" ) ;
2011-10-18 21:15:32 +02:00
}
2011-09-08 01:32:24 +02:00
self . options . value . app = this . app _select . val ( ) ;
// Search input
2012-04-11 17:43:33 +02:00
this . search = $j ( document . createElement ( "input" ) )
// .attr("type", "search") // Fake it for all browsers below
2012-04-06 00:30:06 +02:00
. focus ( function ( ) { if ( ! self . options . application ) {
// Adjust width, leave room for app select & link button
2012-04-24 23:27:48 +02:00
self . div . removeClass ( "no_app" ) ; self . app _select . show ( ) ;
2012-04-06 00:30:06 +02:00
} } )
2011-09-09 02:05:46 +02:00
. appendTo ( this . div ) ;
2011-09-08 01:32:24 +02:00
2012-03-02 11:44:56 +01:00
this . set _blur ( this . options . blur ? this . options . blur : this . egw ( ) . lang ( "search" ) , this . search ) ;
2011-09-09 02:05:46 +02:00
// Autocomplete
2011-09-08 01:32:24 +02:00
this . search . autocomplete ( {
source : function ( request , response ) { return self . query ( request , response ) ; } ,
select : function ( event , item ) { event . data = self ; self . select ( event , item ) ; return false ; } ,
focus : function ( event , item ) {
event . stopPropagation ( ) ;
self . search . val ( item . item . label ) ;
return false ;
} ,
minLength : self . minimum _characters ,
disabled : self . options . disabled
} ) ;
2012-06-06 20:47:04 +02:00
// Custom display (colors)
this . search . data ( "autocomplete" ) . _renderItem = function ( ul , item ) {
var li = jQuery ( document . createElement ( 'li' ) )
. data ( "item.autocomplete" , item ) ;
var extra = { } ;
// Extra stuff
if ( typeof item . label == 'object' ) {
extra = item . label ;
item . label = extra . label ? extra . label : extra ;
if ( extra [ 'style.backgroundColor' ] || extra . color )
{
li . css ( 'backgroundColor' , extra . color ? extra . color : extra [ 'style.backgroundColor' ] ) ;
}
// Careful with this, some browsers may have trouble loading all at once, which can slow display
if ( extra . icon )
{
var img = self . egw ( ) . image ( extra . icon ) ;
if ( img )
{
jQuery ( document . createElement ( "img" ) )
. attr ( "src" , img )
. css ( "float" , "right" )
. appendTo ( li ) ;
}
}
}
// Normal stuff
li . append ( jQuery ( "<a></a>" ) . text ( item . label ) )
. appendTo ( ul ) ;
return li ;
} ;
2011-09-09 02:05:46 +02:00
2012-04-06 00:57:38 +02:00
// Bind to enter key to start search early
this . search . keydown ( function ( e ) {
var keycode = ( e . keyCode ? e . keyCode : e . which ) ;
if ( keycode == '13' )
{
self . search . autocomplete ( "option" , "minLength" , 0 ) ;
self . search . autocomplete ( "search" ) ;
self . search . autocomplete ( "option" , "minLength" , self . minimum _characters ) ;
e . stopPropagation ( ) ;
}
} ) ;
2012-04-11 17:43:33 +02:00
// Clear / last button
2012-04-25 00:00:18 +02:00
this . clear = $j ( document . createElement ( "span" ) )
2012-05-03 00:28:23 +02:00
. addClass ( "ui-icon ui-icon-close" )
2012-04-11 17:43:33 +02:00
. click ( function ( e ) {
// No way to tell if the results is open, so if they click the button while open, it clears
if ( self . last _search && self . last _search != self . search . val ( ) )
{
// Repeat last search (should be cached)
self . search . val ( self . last _search ) ;
self . last _search = "" ;
self . search . autocomplete ( "search" ) ;
}
else
{
// Clear
self . search . autocomplete ( "close" ) ;
2012-06-19 00:41:54 +02:00
self . set _value ( "" ) ;
self . search . trigger ( "change" ) ;
2012-04-11 17:43:33 +02:00
}
self . search . focus ( ) ;
} )
2012-06-20 01:30:07 +02:00
. appendTo ( this . div )
. hide ( ) ;
2012-04-11 17:43:33 +02:00
2011-09-08 01:32:24 +02:00
this . setDOMNode ( this . div [ 0 ] ) ;
} ,
2011-09-13 01:43:39 +02:00
getDOMNode : function ( ) {
return this . div [ 0 ] ;
2011-09-09 02:05:46 +02:00
} ,
2011-09-08 01:32:24 +02:00
transformAttributes : function ( _attrs ) {
this . _super . apply ( this , arguments ) ;
2011-09-13 01:43:39 +02:00
2011-10-18 21:15:32 +02:00
_attrs [ "select_options" ] = { } ;
if ( _attrs [ "application" ] )
{
var apps = et2 _csvSplit ( _attrs [ "application" ] , null , "," ) ;
for ( var i = 0 ; i < apps . length ; i ++ )
{
2012-03-02 11:44:56 +01:00
_attrs [ "select_options" ] [ apps [ i ] ] = this . egw ( ) . lang ( apps [ i ] ) ;
2011-10-18 21:15:32 +02:00
}
}
else
{
2012-03-02 11:44:56 +01:00
_attrs [ "select_options" ] = this . egw ( ) . link _app _list ( 'query' ) ;
2011-10-18 21:15:32 +02:00
}
2011-09-08 01:32:24 +02:00
// Check whether the options entry was found, if not read it from the
// content array.
if ( _attrs [ "select_options" ] == null )
{
_attrs [ "select_options" ] = this . getArrayMgr ( 'content' )
2011-09-08 20:36:09 +02:00
. getEntry ( "options-" + this . id )
2011-09-08 01:32:24 +02:00
}
// Default to an empty object
if ( _attrs [ "select_options" ] == null )
{
_attrs [ "select_options" ] = { } ;
}
} ,
getValue : function ( ) {
2012-06-20 01:30:07 +02:00
return this . options . application ? this . options . value . id : this . options . value ;
2011-09-08 01:32:24 +02:00
} ,
2011-09-14 22:36:39 +02:00
set _value : function ( _value ) {
if ( typeof _value == 'string' )
{
2012-06-11 18:35:46 +02:00
if ( _value . indexOf ( "," ) > 0 ) _value = _value . replace ( "," , ":" ) ;
2011-09-14 22:36:39 +02:00
if ( _value . indexOf ( ":" ) >= 0 )
{
var split = et2 _csvSplit ( _value , 2 , ":" ) ;
_value = {
app : split [ 0 ] ,
id : split [ 1 ]
} ;
}
else if ( _value && this . options . application )
{
_value = {
app : this . options . application ,
id : _value
} ;
}
}
2012-06-26 01:05:18 +02:00
this . _oldValue = this . options . value ;
2012-06-20 01:30:07 +02:00
if ( ! _value || _value . length == 0 || _value == null || jQuery . isEmptyObject ( _value ) )
2012-06-19 00:41:54 +02:00
{
this . search . val ( "" ) ;
this . clear . hide ( ) ;
2012-06-20 01:30:07 +02:00
this . options . value = _value = { 'id' : null } ;
2012-06-19 00:41:54 +02:00
}
2011-09-14 22:36:39 +02:00
if ( ! _value . app ) _value . app = this . options . application ;
2012-06-26 01:05:18 +02:00
2012-06-20 01:30:07 +02:00
if ( _value . id ) {
this . clear . show ( ) ;
} else {
this . clear . hide ( ) ;
return ;
}
if ( typeof _value != 'object' || ( ! _value . app && ! _value . id ) )
2011-09-14 22:36:39 +02:00
{
console . warn ( "Bad value for link widget. Need an object with keys 'app', 'id', and optionally 'title'" , _value ) ;
return ;
}
if ( ! _value . title ) {
2012-03-02 11:44:56 +01:00
var title = this . egw ( ) . link _title ( _value . app , _value . id ) ;
2011-09-14 22:36:39 +02:00
if ( title != null ) {
_value . title = title ;
}
else
{
// Title will be fetched from server and then set
2012-04-05 22:02:29 +02:00
var title = this . egw ( ) . link _title ( _value . app , _value . id , function ( title ) {
2012-04-25 00:00:18 +02:00
this . search . removeClass ( "loading" ) . val ( title + "" ) ;
this . clear . show ( ) ;
} , this ) ;
2012-04-05 22:02:29 +02:00
this . search . addClass ( "loading" ) ;
2011-09-14 22:36:39 +02:00
}
}
2012-04-16 23:52:31 +02:00
if ( _value . title )
2012-04-05 22:02:29 +02:00
{
this . search . val ( _value . title + "" ) ;
}
2012-06-20 01:30:07 +02:00
this . options . value = _value ;
2012-04-05 22:02:29 +02:00
2011-09-14 22:36:39 +02:00
jQuery ( "option[value='" + _value . app + "']" , this . app _select ) . attr ( "selected" , true ) ;
this . app _select . hide ( ) ;
2012-04-24 23:27:48 +02:00
this . div . addClass ( "no_app" ) ;
2011-09-14 22:36:39 +02:00
} ,
2011-09-09 02:05:46 +02:00
set _blur : function ( _value , input ) {
2011-09-13 01:43:39 +02:00
2011-09-09 02:05:46 +02:00
if ( typeof input == 'undefined' ) input = this . search ;
2011-09-08 01:32:24 +02:00
if ( _value ) {
2011-09-09 02:05:46 +02:00
input . attr ( "placeholder" , _value ) ; // HTML5
if ( ! input [ 0 ] . placeholder ) {
2011-09-08 01:32:24 +02:00
// Not HTML5
2011-09-13 01:43:39 +02:00
if ( input . val ( ) == "" ) input . val ( _value ) ;
2011-09-09 02:05:46 +02:00
input . focus ( input , function ( e ) {
var placeholder = _value ;
if ( e . data . val ( ) == placeholder ) e . data . val ( "" ) ;
} ) . blur ( input , function ( e ) {
var placeholder = _value ;
if ( e . data . val ( ) == "" ) e . data . val ( placeholder ) ;
2011-09-08 01:32:24 +02:00
} ) ;
2011-09-09 02:05:46 +02:00
if ( input . val ( ) == "" ) input . val ( _value ) ;
2011-09-08 01:32:24 +02:00
}
} else {
this . search . removeAttr ( "placeholder" ) ;
}
} ,
/ * *
* Ask server for entries matching selected app / type and filtered by search string
* /
query : function ( request , response ) {
2012-04-11 17:43:33 +02:00
// Remember last search
this . last _search = this . search . val ( ) ;
2011-09-13 01:43:39 +02:00
// Allow hook / tie in
if ( this . options . query && typeof this . options . query == 'function' )
{
if ( ! this . options . query ( request , response ) ) return false ;
}
2012-05-30 00:25:40 +02:00
if ( request . term in this . cache ) {
return response ( this . cache [ request . term ] ) ;
}
2011-09-08 01:32:24 +02:00
this . search . addClass ( "loading" ) ;
2012-04-25 00:00:18 +02:00
this . clear . show ( ) ;
2011-09-08 01:32:24 +02:00
var request = new egw _json _request ( "etemplate_widget_link::ajax_link_search::etemplate" ,
2012-05-30 00:25:40 +02:00
[ this . app _select . val ( ) , '' , request . term , request . options ] ,
2011-09-08 01:32:24 +02:00
this
) ;
this . response = response ;
request . sendRequest ( true , this . _results , this ) ;
} ,
/ * *
* User selected a value
* /
select : function ( event , selected ) {
2011-09-13 01:43:39 +02:00
if ( this . options . select && typeof this . options . select == 'function' )
{
if ( ! this . options . select ( event , selected ) ) return false ;
}
2012-06-20 01:30:07 +02:00
if ( typeof event . data . options . value != 'object' || event . data . options . value == null )
2012-03-23 00:20:56 +01:00
{
2012-06-20 01:30:07 +02:00
event . data . options . value = { } ;
2012-03-23 00:20:56 +01:00
}
2012-06-20 01:30:07 +02:00
event . data . options . value . id = selected . item . value ;
2012-04-11 17:43:33 +02:00
2012-04-25 00:00:18 +02:00
this . clear . show ( ) ;
2011-09-08 01:32:24 +02:00
event . data . search . val ( selected . item . label ) ;
2011-09-13 01:43:39 +02:00
2012-06-19 00:41:54 +02:00
// Fire change event
this . search . change ( ) ;
2011-09-08 01:32:24 +02:00
} ,
/ * *
* Server found some results
* /
_results : function ( data ) {
this . search . removeClass ( "loading" ) ;
var result = [ ] ;
for ( var id in data ) {
result . push ( { "value" : id , "label" : data [ id ] } ) ;
}
this . cache [ this . search . val ( ) ] = result ;
this . response ( result ) ;
} ,
/ * *
* Create a link using the current internal values
* /
2011-09-13 01:43:39 +02:00
createLink : function ( event , _links ) {
2011-09-09 02:05:46 +02:00
var values = event . data . options . value ;
var self = event . data ;
2011-09-30 18:19:09 +02:00
var links = [ ] ;
2011-09-09 02:05:46 +02:00
2011-09-13 01:43:39 +02:00
if ( typeof _links == 'undefined' )
{
links = [ ] ;
2011-09-14 02:06:04 +02:00
}
else
{
2011-09-13 01:43:39 +02:00
links = _links ;
}
2011-09-09 02:05:46 +02:00
// Links to other entries
if ( values . id ) {
links . push ( {
app : values . app ,
id : values . id ,
} ) ;
self . search . val ( "" ) ;
}
2011-09-13 01:43:39 +02:00
// If a link array was passed in, don't make the ajax call
if ( typeof _links == 'undefined' )
{
var request = new egw _json _request ( "etemplate_widget_link::ajax_link::etemplate" ,
[ values . to _app , values . to _id , links ] ,
this
) ;
request . sendRequest ( true , self . _link _result , self ) ;
2011-09-09 02:05:46 +02:00
}
} ,
/ * *
* Sent some links , server has a result
* /
_link _result : function ( success ) {
if ( success ) {
this . link _button . hide ( ) . attr ( "disabled" , false ) ;
this . status _span . fadeIn ( ) . delay ( 1000 ) . fadeOut ( ) ;
delete this . options . value . app ;
delete this . options . value . id ;
}
2011-09-08 01:32:24 +02:00
}
} ) ;
2011-09-13 01:43:39 +02:00
et2 _register _widget ( et2 _link _entry , [ "link-entry" ] ) ;
2011-09-08 01:32:24 +02:00
2011-09-13 01:43:39 +02:00
/ * *
* UI widget for a single ( read - only ) link
* /
var et2 _link = et2 _valueWidget . extend ( [ et2 _IDetachedDOM ] , {
attributes : {
"application" : {
"name" : "Application" ,
"type" : "string" ,
"default" : "" ,
"description" : "Use the given application, so you can pass just the ID for value"
} ,
"value" : {
description : "Array with keys app, id, and optionally title" ,
type : "any"
}
} ,
2012-07-09 18:54:58 +02:00
legacyOptions : [ "application" ] ,
2011-09-13 01:43:39 +02:00
init : function ( ) {
this . _super . apply ( this , arguments ) ;
2011-09-22 21:02:33 +02:00
this . link = $j ( document . createElement ( "span" ) )
2011-09-13 01:43:39 +02:00
. addClass ( "et2_link" ) ;
2012-03-22 17:24:48 +01:00
if ( this . options [ 'class' ] ) this . link . addClass ( this . options [ 'class' ] ) ;
2011-09-22 21:02:33 +02:00
this . setDOMNode ( this . link [ 0 ] ) ;
2011-09-13 01:43:39 +02:00
} ,
destroy : function ( ) {
2012-05-30 01:05:26 +02:00
if ( this . link ) this . link . unbind ( ) ;
2011-09-22 21:02:33 +02:00
this . link = null ;
2011-09-13 01:43:39 +02:00
} ,
set _value : function ( _value ) {
2011-10-18 22:09:48 +02:00
if ( typeof _value != 'object' && _value && ! this . options . application )
{
2012-05-24 19:49:28 +02:00
if ( _value . indexOf ( ':' ) >= 0 )
{
var app = _value . split ( ':' , 1 ) ;
var id = _value . substr ( app [ 0 ] . length + 1 ) ;
_value = { 'app' : app [ 0 ] , 'id' : id } ;
}
else
{
console . warn ( "Bad value for link widget. Need an object with keys 'app', 'id', and optionally 'title'" , _value ) ;
return ;
}
2011-09-13 01:43:39 +02:00
}
2011-10-18 22:09:48 +02:00
// Application set, just passed ID
2012-03-23 00:20:56 +01:00
else if ( typeof _value != "object" )
2011-09-13 01:43:39 +02:00
{
2011-10-18 22:09:48 +02:00
_value = {
app : this . options . application ,
id : _value
} ;
}
if ( ! _value || jQuery . isEmptyObject ( _value ) ) {
this . link . text ( "" ) . unbind ( ) ;
2011-09-13 01:43:39 +02:00
return ;
}
2011-09-14 02:06:04 +02:00
if ( ! _value . title ) {
2011-10-18 22:09:48 +02:00
var self = this ;
2012-03-14 20:00:38 +01:00
var node = this . link [ 0 ] ;
var title = this . egw ( ) . link _title ( _value . app , _value . id , function ( title ) { self . set _title ( node , title ) ; } , this ) ;
2011-09-14 02:06:04 +02:00
if ( title != null ) {
_value . title = title ;
}
else
{
// Title will be fetched from server and then set
return ;
}
}
2011-10-18 22:09:48 +02:00
this . set _title ( this . link , _value . title ) ;
2012-03-02 13:41:29 +01:00
var self = this ;
2011-10-18 22:09:48 +02:00
this . link . unbind ( )
2012-03-21 17:02:50 +01:00
. click ( function ( ) { self . egw ( ) . open ( _value , "" , "edit" ) ; } ) ;
2011-09-13 01:43:39 +02:00
} ,
2011-10-18 22:09:48 +02:00
/ * *
* Sets the text to be displayed .
* Used as a callback , so node is provided to make sure we get the right one
* /
set _title : function ( node , _value ) {
2012-03-12 23:32:13 +01:00
if ( _value === false || _value === null ) _value = "" ;
2011-10-18 22:09:48 +02:00
jQuery ( node ) . text ( _value + "" ) ;
} ,
2011-09-13 01:43:39 +02:00
/ * *
* Creates a list of attributes which can be set when working in the
* "detached" mode . The result is stored in the _attrs array which is provided
* by the calling code .
* /
getDetachedAttributes : function ( _attrs ) {
_attrs . push ( "value" ) ;
} ,
/ * *
* Returns an array of DOM nodes . The ( relatively ) same DOM - Nodes have to be
* passed to the "setDetachedAttributes" function in the same order .
* /
getDetachedNodes : function ( ) {
return [ this . node ] ;
} ,
/ * *
* Sets the given associative attribute - > value array and applies the
* attributes to the given DOM - Node .
*
* @ param _nodes is an array of nodes which have to be in the same order as
* the nodes returned by "getDetachedNodes"
* @ param _values is an associative array which contains a subset of attributes
* returned by the "getDetachedAttributes" function and sets them to the
* given values .
* /
setDetachedAttributes : function ( _nodes , _values ) {
2012-03-14 20:00:38 +01:00
this . link = jQuery ( _nodes [ 0 ] ) ;
if ( typeof _values [ "id" ] !== "undefined" ) this . set _id ( _values [ 'id' ] ) ;
2012-03-12 23:32:13 +01:00
if ( typeof _values [ "value" ] !== "undefined" && typeof _values [ "value" ] . title !== "undefined" )
{
// Direct route
this . set _title ( _nodes [ 0 ] , _values [ "value" ] . title ) ;
}
else
{
this . set _value ( _values [ "value" ] ) ;
}
2011-09-13 01:43:39 +02:00
}
} ) ;
2011-10-18 22:09:48 +02:00
et2 _register _widget ( et2 _link , [ "link" , "link-entry_ro" ] ) ;
2011-09-13 01:43:39 +02:00
/ * *
* UI widget for one or more links , comma separated
* /
var et2 _link _string = et2 _valueWidget . extend ( [ et2 _IDetachedDOM ] , {
attributes : {
"application" : {
"name" : "Application" ,
"type" : "string" ,
"default" : "" ,
"description" : "Use the given application, so you can pass just the ID for value"
} ,
"value" : {
2011-09-14 02:06:04 +02:00
"description" : "Either an array of link information (see egw_link::link()) or array with keys to_app and to_id" ,
"type" : "any"
} ,
"only_app" : {
"name" : "Application filter" ,
"type" : "string" ,
"default" : "" ,
"description" : "Appname, eg. 'projectmananager' to list only linked projects"
} ,
"link_type" : {
"name" : "Type filter" ,
"type" : "string" ,
"default" : "" ,
"description" : "Sub-type key to list only entries of that type"
2011-09-13 01:43:39 +02:00
}
} ,
init : function ( ) {
this . _super . apply ( this , arguments ) ;
2011-09-08 01:32:24 +02:00
2011-09-14 02:06:04 +02:00
this . list = $j ( document . createElement ( "ul" ) )
2011-09-13 01:43:39 +02:00
. addClass ( "et2_link_string" ) ;
2011-09-08 01:32:24 +02:00
2012-05-01 01:22:48 +02:00
if ( this . options [ 'class' ] ) this . list . addClass ( this . options [ 'class' ] ) ;
2011-09-14 02:06:04 +02:00
this . setDOMNode ( this . list [ 0 ] ) ;
2011-09-13 01:43:39 +02:00
} ,
destroy : function ( ) {
this . _super . apply ( this , arguments ) ;
2012-03-01 11:13:12 +01:00
if ( this . node != null ) {
this . node . children ( ) . unbind ( ) ;
}
2011-09-13 01:43:39 +02:00
} ,
set _value : function ( _value ) {
// Get data
2012-05-30 00:25:40 +02:00
if ( ! _value || _value == null )
{
this . list . empty ( ) ;
return ;
}
2012-05-30 01:15:58 +02:00
if ( typeof _value == "string" && _value . indexOf ( ',' ) > 0 )
{
_value = _value . split ( ',' ) ;
}
2012-04-17 01:13:48 +02:00
if ( ! _value . to _app && typeof _value == "object" && this . options . application )
{
_value . to _app = this . options . application ;
}
2011-09-14 02:06:04 +02:00
if ( typeof _value == 'object' && _value . to _app && _value . to _id )
{
this . value = _value ;
this . _get _links ( ) ;
return ;
2011-09-13 01:43:39 +02:00
}
2012-05-30 00:25:40 +02:00
this . list . empty ( ) ;
2012-04-17 01:13:48 +02:00
if ( typeof _value == 'object' && _value . length > 0 ) {
2011-09-14 02:06:04 +02:00
// Have full info
// Don't store new value, just update display
2011-09-13 01:43:39 +02:00
// Make new links
2011-09-14 02:06:04 +02:00
for ( var i = 0 ; i < _value . length ; i ++ )
{
if ( ! this . options . only _app || this . options . only _app && _value [ i ] . app == this . options . only _app )
{
2012-04-17 20:33:38 +02:00
this . _add _link ( _value [ i ] . id ? _value [ i ] : { id : _value [ i ] , app : _value . to _app } ) ;
2011-09-14 02:06:04 +02:00
}
2011-09-13 01:43:39 +02:00
}
}
2012-04-17 01:13:48 +02:00
else if ( this . options . application )
{
this . _add _link ( { id : _value , app : this . options . application } ) ;
}
2011-09-13 01:43:39 +02:00
} ,
2011-09-14 02:06:04 +02:00
_get _links : function ( ) {
var _value = this . value ;
// Just IDs - get from server
if ( this . options . only _app )
{
_value . only _app = this . options . only _app ;
}
2012-03-02 11:44:56 +01:00
this . egw ( ) . jsonq ( 'etemplate.etemplate_widget_link.ajax_link_list' , [ _value ] , this . set _value , this ) ;
2011-09-14 02:06:04 +02:00
return ;
} ,
2011-09-13 01:43:39 +02:00
_add _link : function ( _link _data ) {
2012-03-02 13:41:29 +01:00
var self = this ;
2011-09-14 02:06:04 +02:00
var link = $j ( document . createElement ( "li" ) )
. appendTo ( this . list )
2012-06-20 01:30:07 +02:00
. addClass ( "et2_link loading" )
2012-03-21 17:02:50 +01:00
. click ( function ( ) { self . egw ( ) . open ( _link _data , "" , "edit" ) ; } ) ;
2011-09-22 23:40:21 +02:00
if ( _link _data . title ) link . text ( _link _data . title ) ;
2011-09-14 02:06:04 +02:00
// Now that link is created, get title from server & update
if ( ! _link _data . title ) {
2012-06-20 01:30:07 +02:00
this . egw ( ) . link _title ( _link _data . app , _link _data . id , function ( title ) {
this . removeClass ( "loading" ) . text ( title ) ;
} , link ) ;
2011-09-14 02:06:04 +02:00
}
2011-09-13 01:43:39 +02:00
} ,
/ * *
* Creates a list of attributes which can be set when working in the
* "detached" mode . The result is stored in the _attrs array which is provided
* by the calling code .
* /
getDetachedAttributes : function ( _attrs ) {
_attrs . push ( "value" ) ;
} ,
/ * *
* Returns an array of DOM nodes . The ( relatively ) same DOM - Nodes have to be
* passed to the "setDetachedAttributes" function in the same order .
* /
getDetachedNodes : function ( ) {
2011-09-14 02:06:04 +02:00
return [ this . list [ 0 ] ] ;
2011-09-13 01:43:39 +02:00
} ,
/ * *
* Sets the given associative attribute - > value array and applies the
* attributes to the given DOM - Node .
*
* @ param _nodes is an array of nodes which have to be in the same order as
* the nodes returned by "getDetachedNodes"
* @ param _values is an associative array which contains a subset of attributes
* returned by the "getDetachedAttributes" function and sets them to the
* given values .
* /
setDetachedAttributes : function ( _nodes , _values ) {
2011-09-14 02:06:04 +02:00
this . list = $j ( _nodes [ 0 ] ) ;
2011-09-13 01:43:39 +02:00
this . set _value ( _values [ "value" ] ) ;
}
} ) ;
et2 _register _widget ( et2 _link _string , [ "link-string" ] ) ;
2011-09-14 02:06:04 +02:00
/ * *
* UI widget for one or more links in a list ( table )
* /
var et2 _link _list = et2 _link _string . extend ( {
attributes : {
"show_deleted" : {
"name" : "Show deleted" ,
"type" : "boolean" ,
"default" : false ,
"description" : "Show links that are marked as deleted, being held for purge"
}
} ,
init : function ( ) {
this . _super . apply ( this , arguments ) ;
this . list = $j ( document . createElement ( "table" ) )
. addClass ( "et2_link_list" ) ;
2012-03-22 17:24:48 +01:00
if ( this . options [ 'class' ] ) this . node . addClass ( this . options [ 'class' ] ) ;
2011-09-14 02:06:04 +02:00
this . setDOMNode ( this . list [ 0 ] ) ;
2012-06-19 20:59:53 +02:00
// Set up context menu
var self = this ;
this . context = new egwMenu ( ) ;
this . context . addItem ( "comment" , this . egw ( ) . lang ( "Comment" ) , "" , function ( ) {
var link _id = self . context . data . link _id ;
var comment = prompt ( self . egw ( ) . lang ( "Comment" ) ) ;
if ( comment !== null )
{
var remark = jQuery ( '#link_' + link _id , self . list ) . children ( '.remark' ) ;
remark . addClass ( "loading" ) ;
var request = new egw _json _request ( "etemplate_widget_link::ajax_link_comment::etemplate" ,
[ link _id , comment ] ,
this
) ;
request . sendRequest ( true , function ( ) {
if ( remark )
{
remark . removeClass ( "loading" ) . text ( comment + "" ) ;
}
} , self ) ;
}
} ) ;
2012-07-09 21:05:06 +02:00
this . context . addItem ( "file_info" , this . egw ( ) . lang ( "File information" ) , this . egw ( ) . image ( "edit" ) , function ( menu _item ) {
var link _data = self . context . data ;
if ( link _data . app == 'file' )
{
var url = self . egw ( ) . mime _open ( link _data ) ;
if ( typeof url == 'string' && url . indexOf ( 'webdav.php' ) )
{
// URL is url to file in webdav, so get rid of that part
url = url . replace ( '/webdav.php' , '' ) ;
}
else if ( typeof url == 'object' && url . path )
{
url = url . path ;
}
self . egw ( ) . open ( url , "filemanager" , "edit" ) ;
}
} ) ;
this . context . addItem ( "-" , "-" ) ;
2012-06-19 20:59:53 +02:00
this . context . addItem ( "delete" , this . egw ( ) . lang ( "Delete link" ) , this . egw ( ) . image ( "delete" ) , function ( menu _item ) {
var link _id = self . context . data . link _id ;
self . _delete _link ( link _id ) ;
} ) ;
2011-09-14 02:06:04 +02:00
} ,
_add _link : function ( _link _data ) {
var row = $j ( document . createElement ( "tr" ) )
2012-06-19 20:59:53 +02:00
. attr ( "id" , "link_" + _link _data . link _id )
2011-09-14 02:06:04 +02:00
. appendTo ( this . list )
// Icon
var icon = $j ( document . createElement ( "td" ) )
. appendTo ( row )
. addClass ( "icon" ) ;
if ( _link _data . icon )
{
var icon _widget = et2 _createWidget ( "image" ) ;
2012-04-04 21:15:07 +02:00
var src = '' ;
if ( _link _data . type )
{
// VFS - file
src = this . egw ( ) . mime _icon ( _link _data . type , _link _data . icon ) ;
}
else
{
src = this . egw ( ) . image ( _link _data . icon ) ;
}
if ( src ) icon _widget . set _src ( src ) ;
2011-09-14 02:06:04 +02:00
icon . append ( icon _widget . getDOMNode ( ) ) ;
}
2012-04-04 21:21:13 +02:00
var columns = [ 'title' , 'remark' ] ;
2012-04-04 21:15:07 +02:00
2012-03-02 13:41:29 +01:00
var self = this ;
2011-09-14 02:06:04 +02:00
for ( var i = 0 ; i < columns . length ; i ++ ) {
$j ( document . createElement ( "td" ) )
. appendTo ( row )
. addClass ( columns [ i ] )
2012-03-21 17:02:50 +01:00
. click ( function ( ) { self . egw ( ) . open ( _link _data , "" , "edit" ) ; } )
2011-09-23 01:03:37 +02:00
. text ( _link _data [ columns [ i ] ] ) ;
2011-09-14 02:06:04 +02:00
}
// Date
/ *
var date _row = $j ( document . createElement ( "td" ) )
. appendTo ( row ) ;
if ( _link _data . lastmod )
{
var date _widget = et2 _createWidget ( "date-since" ) ;
date _widget . set _value ( _link _data . lastmod ) ;
date _row . append ( date _widget . getDOMNode ( ) ) ;
}
* /
// Delete
var delete _button = $j ( document . createElement ( "td" ) )
. appendTo ( row )
. addClass ( "delete icon" )
2012-06-19 20:59:53 +02:00
. bind ( 'click' , function ( ) { self . _delete _link ( _link _data . link _id ) ; } ) ;
// Context menu
row . bind ( "contextmenu" , function ( e ) {
2012-07-09 21:05:06 +02:00
// File info only available for files
self . context . getItem ( "file_info" ) . set _enabled ( _link _data . app == 'file' ) ;
2012-06-19 20:59:53 +02:00
self . context . data = _link _data ;
self . context . showAt ( e . pageX , e . pageY , true ) ;
e . preventDefault ( ) ;
} ) ;
} ,
_delete _link : function ( link _id ) {
var row = jQuery ( '#link_' + link _id , this . list ) ;
var delete _button = jQuery ( '.delete.icon' , row ) ;
delete _button . addClass ( "loading" ) . removeClass ( "delete" ) ;
new egw _json _request ( "etemplate.etemplate_widget_link.ajax_delete" , [ link _id ] )
. sendRequest ( true , function ( data ) { if ( data ) { row . slideUp ( row . remove ) ; } } ) ;
2011-09-14 02:06:04 +02:00
}
} ) ;
et2 _register _widget ( et2 _link _list , [ "link-list" ] ) ;
2011-09-14 22:36:39 +02:00
/ * *
* UI widget for one or more links in a list ( table )
* /
var et2 _link _add = et2 _inputWidget . extend ( {
attributes : {
"application" : {
"name" : "Application" ,
"type" : "string" ,
"default" : "" ,
"description" : "Limit to the listed application or applications (comma seperated)"
}
} ,
init : function ( ) {
this . _super . apply ( this , arguments ) ;
2012-03-02 11:44:56 +01:00
this . div = jQuery ( document . createElement ( "div" ) ) . text ( this . egw ( ) . lang ( "Add new" ) ) ;
2011-09-14 22:36:39 +02:00
this . setDOMNode ( this . div [ 0 ] ) ;
} ,
doLoadingFinished : function ( ) {
this . _super . apply ( this , arguments ) ;
2012-07-05 00:41:51 +02:00
this . app _select = et2 _createWidget ( "link-apps" , jQuery . extend ( { } , this . options , { 'id' : this . options . id + 'app' } ) , this ) ;
2011-09-14 22:36:39 +02:00
this . div . append ( this . app _select . getDOMNode ( ) ) ;
2012-03-02 11:44:56 +01:00
this . button = et2 _createWidget ( "button" , { label : this . egw ( ) . lang ( "add" ) } , this ) ;
this . button . set _label ( this . egw ( ) . lang ( "add" ) ) ;
2011-09-14 22:36:39 +02:00
var self = this ;
this . button . click = function ( ) {
2012-03-02 13:41:29 +01:00
self . egw ( ) . open ( self . options . value . to _app + ":" + self . options . value . to _id , self . app _select . get _value ( ) , 'add' ) ;
2011-09-14 22:36:39 +02:00
} ;
this . div . append ( this . button . getDOMNode ( ) ) ;
2012-06-26 01:05:18 +02:00
} ,
/ * *
* Should be handled client side .
* Return null to avoid overwriting other link values , in case designer used the same ID for multiple widgets
* /
getValue : function ( ) {
return null ;
2011-09-14 22:36:39 +02:00
}
} ) ;
et2 _register _widget ( et2 _link _add , [ "link-add" ] ) ;