Revision 08d86103
Mise à jour de Vigiboard pour utiliser mootools/jxlib à la place de jQuery.
Ajout de l'auto-complétion sur les champs du formulaire de recherche.
Ajout d'échapement dans le RootController pour les paramètres passés dans les liens du menu de détails.
git-svn-id: https://vigilo-dev.si.c-s.fr/svn@1406 b22e2e97-25c9-44ff-b637-2e5ceca36478
vigiboard/config/app_cfg.py | ||
---|---|---|
76 | 76 |
# - idcorrevent : identifiant de l'aggregat (alerte correlee) |
77 | 77 |
# - host : le nom de l'hote concerne par l'alerte |
78 | 78 |
# - service : le nom du service concerne par l'alerte |
79 |
# - message : le message transmis par Nagios dans l'alerte |
|
79 | 80 |
base_config['vigiboard_links.eventdetails'] = { |
80 | 81 |
'nagios': ['Nagios host details', 'http://example1.com/%(idcorrevent)d'], |
81 | 82 |
'metrology': ['Metrology details', 'http://example2.com/%(idcorrevent)d'], |
82 | 83 |
'security': ['Security details', 'http://example3.com/%(idcorrevent)d'], |
83 | 84 |
'servicetype': ['Service Type', 'http://example4.com/%(idcorrevent)d'], |
85 |
'documentation': ['Documentation', 'http://doc.example.com/?q=%(message)s'], |
|
84 | 86 |
} |
85 | 87 |
|
86 | 88 |
# URL des tickets, possibilités: |
vigiboard/config/middleware.py | ||
---|---|---|
44 | 44 |
'vigilo.themes.public', 'vigiboard')) |
45 | 45 |
common_static = StaticURLParser(resource_filename( |
46 | 46 |
'vigilo.themes.public', 'common')) |
47 |
app = Cascade([app_static, common_static, app]) |
|
47 |
local_static = StaticURLParser(resource_filename( |
|
48 |
'vigiboard', 'public')) |
|
49 |
app = Cascade([app_static, common_static, local_static, app]) |
|
48 | 50 |
|
49 | 51 |
return app |
50 | 52 |
|
vigiboard/controllers/root.py | ||
---|---|---|
11 | 11 |
from sqlalchemy import not_, and_, asc |
12 | 12 |
from datetime import datetime |
13 | 13 |
import math |
14 |
import urllib |
|
14 | 15 |
|
15 | 16 |
from vigiboard.model import DBSession |
16 | 17 |
from vigiboard.model import Event, EventHistory, CorrEvent, \ |
... | ... | |
25 | 26 |
|
26 | 27 |
__all__ = ('RootController', ) |
27 | 28 |
|
29 |
def sql_escape_like(s): |
|
30 |
return s.replace('%', '\\%').replace('_', '\\_') \ |
|
31 |
.replace('*', '%').replace('?', '_') |
|
32 |
|
|
28 | 33 |
class RootController(VigiboardRootController): |
29 | 34 |
""" |
30 | 35 |
Le controller général de vigiboard |
... | ... | |
93 | 98 |
'tt': '' |
94 | 99 |
} |
95 | 100 |
# Application des filtres si nécessaire |
96 |
if host :
|
|
101 |
if host: |
|
97 | 102 |
search['host'] = host |
98 |
host = host.replace('%', '\\%').replace('_', '\\_') \ |
|
99 |
.replace('*', '%').replace('?', '_') |
|
100 |
aggregates.add_filter(Event.hostname.ilike('%%%s%%' % host)) |
|
103 |
host = sql_escape_like(host) |
|
104 |
aggregates.add_filter(Host.name.ilike('%%%s%%' % host)) |
|
101 | 105 |
|
102 |
if service :
|
|
106 |
if service: |
|
103 | 107 |
search['service'] = service |
104 |
service = service.replace('%', '\\%').replace('_', '\\_') \ |
|
105 |
.replace('*', '%').replace('?', '_') |
|
106 |
aggregates.add_filter(Event.servicename.ilike('%%%s%%' % service)) |
|
108 |
service = sql_escape_like(service) |
|
109 |
aggregates.add_filter(ServiceLowLevel.servicename.ilike('%%%s%%' % service)) |
|
107 | 110 |
|
108 |
if output :
|
|
111 |
if output: |
|
109 | 112 |
search['output'] = output |
110 |
output = output.replace('%', '\\%').replace('_', '\\_') \ |
|
111 |
.replace('*', '%').replace('?', '_') |
|
113 |
output = sql_escape_like(output) |
|
112 | 114 |
aggregates.add_filter(Event.message.ilike('%%%s%%' % output)) |
113 | 115 |
|
114 |
if trouble_ticket :
|
|
116 |
if trouble_ticket: |
|
115 | 117 |
search['tt'] = trouble_ticket |
116 |
trouble_ticket = trouble_ticket.replace('%', '\\%') \ |
|
117 |
.replace('_', '\\_').replace('*', '%').replace('?', '_') |
|
118 |
trouble_ticket = sql_escape_like(trouble_ticket) |
|
118 | 119 |
aggregates.add_filter(CorrEvent.trouble_ticket.ilike( |
119 | 120 |
'%%%s%%' % trouble_ticket)) |
120 | 121 |
|
... | ... | |
151 | 152 |
refresh_times=self.refresh_times, |
152 | 153 |
) |
153 | 154 |
|
154 |
@validate(validators={'idcorrevent':validators.Int(not_empty=True)}, |
|
155 |
@validate(validators={'idcorrevent': validators.Int(not_empty=True)},
|
|
155 | 156 |
error_handler=process_form_errors) |
156 | 157 |
@expose('json') |
157 | 158 |
@require(Any(not_anonymous(), msg=l_("You need to be authenticated"))) |
... | ... | |
207 | 208 |
for edname, edlink in \ |
208 | 209 |
config['vigiboard_links.eventdetails'].iteritems(): |
209 | 210 |
|
211 |
# Rappel: |
|
212 |
# event[0] = priorité de l'alerte corrélée. |
|
213 |
# event[1] = alerte brute. |
|
210 | 214 |
eventdetails[edname] = edlink[1] % { |
211 |
'idcorrevent': idcorrevent, |
|
212 |
'host': event[1].supitem.host.name, |
|
213 |
'service': event[1].supitem.servicename |
|
214 |
} |
|
215 |
'idcorrevent': idcorrevent, |
|
216 |
'host': urllib.quote(event[1].supitem.host.name), |
|
217 |
'service': urllib.quote(event[1].supitem.servicename), |
|
218 |
'message': urllib.quote(event[1].message), |
|
219 |
} |
|
215 | 220 |
|
216 | 221 |
return dict( |
217 | 222 |
current_state = StateName.value_to_statename( |
... | ... | |
226 | 231 |
eventdetails = eventdetails, |
227 | 232 |
) |
228 | 233 |
|
229 |
@validate(validators={'idcorrevent':validators.Int(not_empty=True)}, |
|
234 |
@validate(validators={'idcorrevent': validators.Int(not_empty=True)},
|
|
230 | 235 |
error_handler=process_form_errors) |
231 | 236 |
@expose('vigiboard.html') |
232 | 237 |
@require(Any(not_anonymous(), msg=l_("You need to be authenticated"))) |
... | ... | |
273 | 278 |
refresh_times=self.refresh_times, |
274 | 279 |
) |
275 | 280 |
|
276 |
@validate(validators={'host':validators.NotEmpty(), |
|
277 |
'service':validators.NotEmpty()}, error_handler=process_form_errors) |
|
281 |
@validate(validators={'host': validators.NotEmpty(),
|
|
282 |
'service': validators.NotEmpty()}, error_handler=process_form_errors)
|
|
278 | 283 |
@expose('vigiboard.html') |
279 | 284 |
@require(Any(not_anonymous(), msg=l_("You need to be authenticated"))) |
280 | 285 |
def host_service(self, host, service): |
... | ... | |
334 | 339 |
@validate(validators={ |
335 | 340 |
"id":validators.Regex(r'^[^,]+(,[^,]*)*,?$'), |
336 | 341 |
# "trouble_ticket":validators.Regex(r'^[0-9]*$'), |
337 |
"status":validators.OneOf(['NoChange', 'None', 'Acknowledged', |
|
338 |
'AAClosed']) |
|
339 |
}, error_handler=process_form_errors) |
|
342 |
"status": validators.OneOf([ |
|
343 |
'NoChange', |
|
344 |
'None', |
|
345 |
'Acknowledged', |
|
346 |
'AAClosed' |
|
347 |
])}, error_handler=process_form_errors) |
|
340 | 348 |
@require(Any(not_anonymous(), msg=l_("You need to be authenticated"))) |
341 | 349 |
def update(self,**krgv): |
342 | 350 |
|
... | ... | |
413 | 421 |
redirect(request.environ.get('HTTP_REFERER', url('/'))) |
414 | 422 |
|
415 | 423 |
|
416 |
@validate(validators={"plugin_name":validators.OneOf( |
|
424 |
@validate(validators={"plugin_name": validators.OneOf(
|
|
417 | 425 |
[i for [i, j] in config.get('vigiboard_plugins', [])])}, |
418 | 426 |
error_handler = process_form_errors) |
419 | 427 |
@expose('json') |
... | ... | |
446 | 454 |
session.save() |
447 | 455 |
return dict(ret= 'ok') |
448 | 456 |
|
449 |
@validate(validators= {"refresh": validators.Int()},
|
|
450 |
error_handler = process_form_errors)
|
|
457 |
@validate(validators={"refresh": validators.Int()}, |
|
458 |
error_handler=process_form_errors)
|
|
451 | 459 |
@expose('json') |
452 | 460 |
def set_refresh(self, refresh): |
453 | 461 |
""" |
... | ... | |
457 | 465 |
session.save() |
458 | 466 |
return dict(ret= 'ok') |
459 | 467 |
|
468 |
@expose('json') |
|
469 |
def autocomplete_host(self, value): |
|
470 |
value = sql_escape_like(value) |
|
471 |
hostnames = DBSession.query( |
|
472 |
Host.name.distinct()).filter( |
|
473 |
Host.name.ilike('%' + value + '%')).all() |
|
474 |
return dict(results=[h[0] for h in hostnames]) |
|
475 |
|
|
476 |
@expose('json') |
|
477 |
def autocomplete_service(self, value): |
|
478 |
value = sql_escape_like(value) |
|
479 |
services = DBSession.query( |
|
480 |
ServiceLowLevel.servicename.distinct()).filter( |
|
481 |
ServiceLowLevel.servicename.ilike('%' + value + '%')).all() |
|
482 |
return dict(results=[s[0] for s in services]) |
|
483 |
|
vigiboard/controllers/vigiboard_plugin/shn.py | ||
---|---|---|
8 | 8 |
VigiboardRequestPlugin |
9 | 9 |
from vigiboard.model import DBSession, CorrEvent |
10 | 10 |
from pylons.i18n import gettext as _ |
11 |
from tg import tmpl_context, url |
|
12 |
from tw.jquery.ui_dialog import JQueryUIDialog |
|
11 |
from tg import url |
|
13 | 12 |
|
14 | 13 |
class PluginSHN(VigiboardRequestPlugin): |
15 | 14 |
|
... | ... | |
35 | 34 |
} |
36 | 35 |
# XXX Il faudrait échapper l'URL contenue dans baseurl |
37 | 36 |
# pour éviter des attaques de type XSS. |
38 |
res = ('<a href="javascript:vigiboard_shndialog(' + \
|
|
39 |
'\'%(baseurl)s\',\'%(idcorrevent)d\')" ' + \
|
|
40 |
'class="SHNLien">%(impacted_hls)d</a>') % dico
|
|
37 |
res = ('<a href="javascript:vigiboard_hls_dialog(this,' + \
|
|
38 |
'\'%(baseurl)s\',%(idcorrevent)d)" ' + \
|
|
39 |
'class="hls_link">%(impacted_hls)d</a>') % dico
|
|
41 | 40 |
return res |
42 | 41 |
|
43 | 42 |
def context(self, context): |
44 | 43 |
"""Fonction de context""" |
45 |
|
|
46 |
# On ajoute 10 espaces insécables pour éviter un bug de JQueryUIDialog: |
|
47 |
# le calcul de la taille de la boîte de dialogue ne tient pas compte |
|
48 |
# de l'espace occupé par la croix permettant de fermer le dialogue. |
|
49 |
# Du coup, elle se retrouve superposée au titre de la boîte. |
|
50 |
tmpl_context.shndialog = JQueryUIDialog(id='SHNDialog', |
|
51 |
autoOpen=False, title='%s%s' % (_(u'High-Level Services'), |
|
52 |
' ' * 10)) |
|
53 |
context.append([tmpl_context.shndialog, self.object_name]) |
|
44 |
context.append([None, self.object_name]) |
|
54 | 45 |
|
55 | 46 |
def controller(self, *argv, **krgv): |
56 | 47 |
"""Ajout de fonctionnalités au contrôleur""" |
57 | 48 |
idcorrevent = krgv['idcorrevent'] |
58 | 49 |
correvent = DBSession.query(CorrEvent) \ |
59 | 50 |
.filter(CorrEvent.idcorrevent == idcorrevent).one() |
60 |
shns = correvent.high_level_services
|
|
51 |
services = correvent.high_level_services
|
|
61 | 52 |
|
62 |
return dict(shns=[shn.servicename for shn in shns])
|
|
53 |
return dict(services=[service.servicename for service in services])
|
|
63 | 54 |
|
vigiboard/controllers/vigiboardrequest.py | ||
---|---|---|
7 | 7 |
StateName |
8 | 8 |
from vigilo.models.secondary_tables import HOST_GROUP_TABLE, \ |
9 | 9 |
SERVICE_GROUP_TABLE |
10 |
from tg import tmpl_context, url, config
|
|
10 |
from tg import url, config, tmpl_context
|
|
11 | 11 |
from vigiboard.model import DBSession |
12 | 12 |
from sqlalchemy import not_, and_, asc, desc, sql |
13 | 13 |
from sqlalchemy.orm import aliased |
14 |
from tw.jquery.ui_dialog import JQueryUIDialog
|
|
15 |
from vigiboard.widgets.edit_event import EditEventForm , SearchForm
|
|
14 |
from vigiboard.widgets.edit_event import EditEventForm
|
|
15 |
from vigiboard.widgets.search_form import SearchForm
|
|
16 | 16 |
from vigiboard.controllers.vigiboard_plugin import VigiboardRequestPlugin |
17 | 17 |
from pylons.i18n import ugettext as _ |
18 | 18 |
|
... | ... | |
441 | 441 |
# Dialogue d'édition |
442 | 442 |
tmpl_context.edit_event_form = EditEventForm('edit_event_form', |
443 | 443 |
action=url('/update')) |
444 |
tmpl_context.edit_eventdialog = JQueryUIDialog(id='Edit_EventsDialog', |
|
445 |
autoOpen=False, title=_('Edit Event')) |
|
446 | 444 |
|
447 | 445 |
# Dialogue de recherche |
448 |
tmpl_context.search_form = SearchForm('search_form', |
|
449 |
action=url('/')) |
|
450 |
tmpl_context.searchdialog = JQueryUIDialog(id='SearchDialog', |
|
451 |
autoOpen=False, title=_('Search Event')) |
|
446 |
tmpl_context.search_form = SearchForm('search_form', action=url('/')) |
|
452 | 447 |
|
453 | 448 |
# Dialogue de détail d'un événement |
454 |
tmpl_context.historydialog = JQueryUIDialog(id='HistoryDialog', |
|
455 |
autoOpen=False, title=_('History')) |
|
456 | 449 |
|
457 | 450 |
# Exécution des contexts des plugins |
458 | 451 |
for j in self.plugin: |
vigiboard/public/css/Autocompleter/Autocompleter.css | ||
---|---|---|
1 |
ul.autocompleter-choices |
|
2 |
{ |
|
3 |
position: absolute; |
|
4 |
margin: 0; |
|
5 |
padding: 0; |
|
6 |
list-style: none; |
|
7 |
border: 1px solid #7c7c7c; |
|
8 |
border-left-color: #c3c3c3; |
|
9 |
border-right-color: #c3c3c3; |
|
10 |
border-bottom-color: #ddd; |
|
11 |
background-color: #fff; |
|
12 |
text-align: left; |
|
13 |
font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; |
|
14 |
z-index: 50; |
|
15 |
background-color: #fff; |
|
16 |
} |
|
17 |
|
|
18 |
ul.autocompleter-choices li |
|
19 |
{ |
|
20 |
position: relative; |
|
21 |
margin: -2px 0 0 0; |
|
22 |
padding: 0.2em 1.5em 0.2em 1em; |
|
23 |
display: block; |
|
24 |
float: none !important; |
|
25 |
cursor: pointer; |
|
26 |
font-weight: normal; |
|
27 |
white-space: nowrap; |
|
28 |
font-size: 1em; |
|
29 |
line-height: 1.5em; |
|
30 |
} |
|
31 |
|
|
32 |
ul.autocompleter-choices li.autocompleter-selected |
|
33 |
{ |
|
34 |
background-color: #444; |
|
35 |
color: #fff; |
|
36 |
} |
|
37 |
|
|
38 |
ul.autocompleter-choices span.autocompleter-queried |
|
39 |
{ |
|
40 |
display: inline; |
|
41 |
float: none; |
|
42 |
font-weight: bold; |
|
43 |
margin: 0; |
|
44 |
padding: 0; |
|
45 |
} |
|
46 |
|
|
47 |
ul.autocompleter-choices li.autocompleter-selected span.autocompleter-queried |
|
48 |
{ |
|
49 |
color: #9FCFFF; |
|
50 |
} |
vigiboard/public/js/Autocompleter/Autocompleter.Local.js | ||
---|---|---|
1 |
/** |
|
2 |
* Autocompleter.Local |
|
3 |
* |
|
4 |
* http://digitarald.de/project/autocompleter/ |
|
5 |
* |
|
6 |
* @version 1.1.2 |
|
7 |
* |
|
8 |
* @license MIT-style license |
|
9 |
* @author Harald Kirschner <mail [at] digitarald.de> |
|
10 |
* @copyright Author |
|
11 |
*/ |
|
12 |
|
|
13 |
Autocompleter.Local = new Class({ |
|
14 |
|
|
15 |
Extends: Autocompleter, |
|
16 |
|
|
17 |
options: { |
|
18 |
minLength: 0, |
|
19 |
delay: 200 |
|
20 |
}, |
|
21 |
|
|
22 |
initialize: function(element, tokens, options) { |
|
23 |
this.parent(element, options); |
|
24 |
this.tokens = tokens; |
|
25 |
}, |
|
26 |
|
|
27 |
query: function() { |
|
28 |
this.update(this.filter()); |
|
29 |
} |
|
30 |
|
|
31 |
}); |
vigiboard/public/js/Autocompleter/Autocompleter.Request.js | ||
---|---|---|
1 |
/** |
|
2 |
* Autocompleter.Request |
|
3 |
* |
|
4 |
* http://digitarald.de/project/autocompleter/ |
|
5 |
* |
|
6 |
* @version 1.1.2 |
|
7 |
* |
|
8 |
* @license MIT-style license |
|
9 |
* @author Harald Kirschner <mail [at] digitarald.de> |
|
10 |
* @copyright Author |
|
11 |
*/ |
|
12 |
|
|
13 |
Autocompleter.Request = new Class({ |
|
14 |
|
|
15 |
Extends: Autocompleter, |
|
16 |
|
|
17 |
options: {/* |
|
18 |
indicator: null, |
|
19 |
indicatorClass: null, |
|
20 |
onRequest: $empty, |
|
21 |
onComplete: $empty,*/ |
|
22 |
postData: {}, |
|
23 |
ajaxOptions: {}, |
|
24 |
postVar: 'value' |
|
25 |
|
|
26 |
}, |
|
27 |
|
|
28 |
query: function(){ |
|
29 |
var data = $unlink(this.options.postData) || {}; |
|
30 |
data[this.options.postVar] = this.queryValue; |
|
31 |
var indicator = $(this.options.indicator); |
|
32 |
if (indicator) indicator.setStyle('display', ''); |
|
33 |
var cls = this.options.indicatorClass; |
|
34 |
if (cls) this.element.addClass(cls); |
|
35 |
this.fireEvent('onRequest', [this.element, this.request, data, this.queryValue]); |
|
36 |
this.request.send({'data': data}); |
|
37 |
}, |
|
38 |
|
|
39 |
/** |
|
40 |
* queryResponse - abstract |
|
41 |
* |
|
42 |
* Inherated classes have to extend this function and use this.parent() |
|
43 |
*/ |
|
44 |
queryResponse: function() { |
|
45 |
var indicator = $(this.options.indicator); |
|
46 |
if (indicator) indicator.setStyle('display', 'none'); |
|
47 |
var cls = this.options.indicatorClass; |
|
48 |
if (cls) this.element.removeClass(cls); |
|
49 |
return this.fireEvent('onComplete', [this.element, this.request]); |
|
50 |
} |
|
51 |
|
|
52 |
}); |
|
53 |
|
|
54 |
Autocompleter.Request.JSON = new Class({ |
|
55 |
|
|
56 |
Extends: Autocompleter.Request, |
|
57 |
|
|
58 |
initialize: function(el, url, options) { |
|
59 |
this.parent(el, options); |
|
60 |
this.request = new Request.JSON($merge({ |
|
61 |
'url': url, |
|
62 |
'link': 'cancel' |
|
63 |
}, this.options.ajaxOptions)).addEvent('onComplete', this.queryResponse.bind(this)); |
|
64 |
}, |
|
65 |
|
|
66 |
queryResponse: function(response) { |
|
67 |
this.parent(); |
|
68 |
this.update(response); |
|
69 |
} |
|
70 |
|
|
71 |
}); |
|
72 |
|
|
73 |
Autocompleter.Request.HTML = new Class({ |
|
74 |
|
|
75 |
Extends: Autocompleter.Request, |
|
76 |
|
|
77 |
initialize: function(el, url, options) { |
|
78 |
this.parent(el, options); |
|
79 |
this.request = new Request.HTML($merge({ |
|
80 |
'url': url, |
|
81 |
'link': 'cancel', |
|
82 |
'update': this.choices |
|
83 |
}, this.options.ajaxOptions)).addEvent('onComplete', this.queryResponse.bind(this)); |
|
84 |
}, |
|
85 |
|
|
86 |
queryResponse: function(tree, elements) { |
|
87 |
this.parent(); |
|
88 |
if (!elements || !elements.length) { |
|
89 |
this.hideChoices(); |
|
90 |
} else { |
|
91 |
this.choices.getChildren(this.options.choicesMatch).each(this.options.injectChoice || function(choice) { |
|
92 |
var value = choice.innerHTML; |
|
93 |
choice.inputValue = value; |
|
94 |
this.addChoiceEvents(choice.set('html', this.markQueryValue(value))); |
|
95 |
}, this); |
|
96 |
this.showChoices(); |
|
97 |
} |
|
98 |
|
|
99 |
} |
|
100 |
|
|
101 |
}); |
|
102 |
|
|
103 |
/* compatibility */ |
|
104 |
|
|
105 |
Autocompleter.Ajax = { |
|
106 |
Base: Autocompleter.Request, |
|
107 |
Json: Autocompleter.Request.JSON, |
|
108 |
Xhtml: Autocompleter.Request.HTML |
|
109 |
}; |
vigiboard/public/js/Autocompleter/Autocompleter.js | ||
---|---|---|
1 |
/** |
|
2 |
* Autocompleter |
|
3 |
* |
|
4 |
* http://digitarald.de/project/autocompleter/ |
|
5 |
* |
|
6 |
* @version 1.1.2 |
|
7 |
* |
|
8 |
* @license MIT-style license |
|
9 |
* @author Harald Kirschner <mail [at] digitarald.de> |
|
10 |
* @copyright Author |
|
11 |
*/ |
|
12 |
|
|
13 |
var Autocompleter = new Class({ |
|
14 |
|
|
15 |
Implements: [Options, Events], |
|
16 |
|
|
17 |
options: {/* |
|
18 |
onOver: $empty, |
|
19 |
onSelect: $empty, |
|
20 |
onSelection: $empty, |
|
21 |
onShow: $empty, |
|
22 |
onHide: $empty, |
|
23 |
onBlur: $empty, |
|
24 |
onFocus: $empty,*/ |
|
25 |
minLength: 1, |
|
26 |
markQuery: true, |
|
27 |
width: 'inherit', |
|
28 |
maxChoices: 10, |
|
29 |
injectChoice: null, |
|
30 |
customChoices: null, |
|
31 |
emptyChoices: null, |
|
32 |
visibleChoices: true, |
|
33 |
className: 'autocompleter-choices', |
|
34 |
zIndex: 42, |
|
35 |
delay: 400, |
|
36 |
observerOptions: {}, |
|
37 |
fxOptions: {}, |
|
38 |
|
|
39 |
autoSubmit: false, |
|
40 |
overflow: false, |
|
41 |
overflowMargin: 25, |
|
42 |
selectFirst: false, |
|
43 |
filter: null, |
|
44 |
filterCase: false, |
|
45 |
filterSubset: false, |
|
46 |
forceSelect: false, |
|
47 |
selectMode: true, |
|
48 |
choicesMatch: null, |
|
49 |
|
|
50 |
multiple: false, |
|
51 |
separator: ', ', |
|
52 |
separatorSplit: /\s*[,;]\s*/, |
|
53 |
autoTrim: false, |
|
54 |
allowDupes: false, |
|
55 |
|
|
56 |
cache: true, |
|
57 |
relative: false |
|
58 |
}, |
|
59 |
|
|
60 |
initialize: function(element, options) { |
|
61 |
this.element = $(element); |
|
62 |
this.setOptions(options); |
|
63 |
this.build(); |
|
64 |
this.observer = new Observer(this.element, this.prefetch.bind(this), $merge({ |
|
65 |
'delay': this.options.delay |
|
66 |
}, this.options.observerOptions)); |
|
67 |
this.queryValue = null; |
|
68 |
if (this.options.filter) this.filter = this.options.filter.bind(this); |
|
69 |
var mode = this.options.selectMode; |
|
70 |
this.typeAhead = (mode == 'type-ahead'); |
|
71 |
this.selectMode = (mode === true) ? 'selection' : mode; |
|
72 |
this.cached = []; |
|
73 |
}, |
|
74 |
|
|
75 |
/** |
|
76 |
* build - Initialize DOM |
|
77 |
* |
|
78 |
* Builds the html structure for choices and appends the events to the element. |
|
79 |
* Override this function to modify the html generation. |
|
80 |
*/ |
|
81 |
build: function() { |
|
82 |
if ($(this.options.customChoices)) { |
|
83 |
this.choices = this.options.customChoices; |
|
84 |
} else { |
|
85 |
this.choices = new Element('ul', { |
|
86 |
'class': this.options.className, |
|
87 |
'styles': { |
|
88 |
'zIndex': this.options.zIndex |
|
89 |
} |
|
90 |
}).inject(document.body); |
|
91 |
this.relative = false; |
|
92 |
if (this.options.relative) { |
|
93 |
this.choices.inject(this.element, 'after'); |
|
94 |
this.relative = this.element.getOffsetParent(); |
|
95 |
} |
|
96 |
this.fix = new OverlayFix(this.choices); |
|
97 |
} |
|
98 |
if (!this.options.separator.test(this.options.separatorSplit)) { |
|
99 |
this.options.separatorSplit = this.options.separator; |
|
100 |
} |
|
101 |
this.fx = (!this.options.fxOptions) ? null : new Fx.Tween(this.choices, $merge({ |
|
102 |
'property': 'opacity', |
|
103 |
'link': 'cancel', |
|
104 |
'duration': 200 |
|
105 |
}, this.options.fxOptions)).addEvent('onStart', Chain.prototype.clearChain).set(0); |
|
106 |
this.element.setProperty('autocomplete', 'off') |
|
107 |
.addEvent((Browser.Engine.trident || Browser.Engine.webkit) ? 'keydown' : 'keypress', this.onCommand.bind(this)) |
|
108 |
.addEvent('click', this.onCommand.bind(this, [false])) |
|
109 |
.addEvent('focus', this.toggleFocus.create({bind: this, arguments: true, delay: 100})) |
|
110 |
.addEvent('blur', this.toggleFocus.create({bind: this, arguments: false, delay: 100})); |
|
111 |
}, |
|
112 |
|
|
113 |
destroy: function() { |
|
114 |
if (this.fix) this.fix.destroy(); |
|
115 |
this.choices = this.selected = this.choices.destroy(); |
|
116 |
}, |
|
117 |
|
|
118 |
toggleFocus: function(state) { |
|
119 |
this.focussed = state; |
|
120 |
if (!state) this.hideChoices(true); |
|
121 |
this.fireEvent((state) ? 'onFocus' : 'onBlur', [this.element]); |
|
122 |
}, |
|
123 |
|
|
124 |
onCommand: function(e) { |
|
125 |
if (!e && this.focussed) return this.prefetch(); |
|
126 |
if (e && e.key && !e.shift) { |
|
127 |
switch (e.key) { |
|
128 |
case 'enter': |
|
129 |
if (this.element.value != this.opted) return true; |
|
130 |
if (this.selected && this.visible) { |
|
131 |
this.choiceSelect(this.selected); |
|
132 |
return !!(this.options.autoSubmit); |
|
133 |
} |
|
134 |
break; |
|
135 |
case 'up': case 'down': |
|
136 |
if (!this.prefetch() && this.queryValue !== null) { |
|
137 |
var up = (e.key == 'up'); |
|
138 |
this.choiceOver((this.selected || this.choices)[ |
|
139 |
(this.selected) ? ((up) ? 'getPrevious' : 'getNext') : ((up) ? 'getLast' : 'getFirst') |
|
140 |
](this.options.choicesMatch), true); |
|
141 |
} |
|
142 |
return false; |
|
143 |
case 'esc': case 'tab': |
|
144 |
this.hideChoices(true); |
|
145 |
break; |
|
146 |
} |
|
147 |
} |
|
148 |
return true; |
|
149 |
}, |
|
150 |
|
|
151 |
setSelection: function(finish) { |
|
152 |
var input = this.selected.inputValue, value = input; |
|
153 |
var start = this.queryValue.length, end = input.length; |
|
154 |
if (input.substr(0, start).toLowerCase() != this.queryValue.toLowerCase()) start = 0; |
|
155 |
if (this.options.multiple) { |
|
156 |
var split = this.options.separatorSplit; |
|
157 |
value = this.element.value; |
|
158 |
start += this.queryIndex; |
|
159 |
end += this.queryIndex; |
|
160 |
var old = value.substr(this.queryIndex).split(split, 1)[0]; |
|
161 |
value = value.substr(0, this.queryIndex) + input + value.substr(this.queryIndex + old.length); |
|
162 |
if (finish) { |
|
163 |
var tokens = value.split(this.options.separatorSplit).filter(function(entry) { |
|
164 |
return this.test(entry); |
|
165 |
}, /[^\s,]+/); |
|
166 |
if (!this.options.allowDupes) tokens = [].combine(tokens); |
|
167 |
var sep = this.options.separator; |
|
168 |
value = tokens.join(sep) + sep; |
|
169 |
end = value.length; |
|
170 |
} |
|
171 |
} |
|
172 |
this.observer.setValue(value); |
|
173 |
this.opted = value; |
|
174 |
if (finish || this.selectMode == 'pick') start = end; |
|
175 |
this.element.selectRange(start, end); |
|
176 |
this.fireEvent('onSelection', [this.element, this.selected, value, input]); |
|
177 |
}, |
|
178 |
|
|
179 |
showChoices: function() { |
|
180 |
var match = this.options.choicesMatch, first = this.choices.getFirst(match); |
|
181 |
this.selected = this.selectedValue = null; |
|
182 |
if (this.fix) { |
|
183 |
var pos = this.element.getCoordinates(this.relative), width = this.options.width || 'auto'; |
|
184 |
this.choices.setStyles({ |
|
185 |
'left': pos.left, |
|
186 |
'top': pos.bottom, |
|
187 |
'width': (width === true || width == 'inherit') ? pos.width : width |
|
188 |
}); |
|
189 |
} |
|
190 |
if (!first) return; |
|
191 |
if (!this.visible) { |
|
192 |
this.visible = true; |
|
193 |
this.choices.setStyle('display', ''); |
|
194 |
if (this.fx) this.fx.start(1); |
|
195 |
this.fireEvent('onShow', [this.element, this.choices]); |
|
196 |
} |
|
197 |
if (this.options.selectFirst || this.typeAhead || first.inputValue == this.queryValue) this.choiceOver(first, this.typeAhead); |
|
198 |
var items = this.choices.getChildren(match), max = this.options.maxChoices; |
|
199 |
var styles = {'overflowY': 'hidden', 'height': ''}; |
|
200 |
this.overflown = false; |
|
201 |
if (items.length > max) { |
|
202 |
var item = items[max - 1]; |
|
203 |
styles.overflowY = 'scroll'; |
|
204 |
styles.height = item.getCoordinates(this.choices).bottom; |
|
205 |
this.overflown = true; |
|
206 |
}; |
|
207 |
this.choices.setStyles(styles); |
|
208 |
this.fix.show(); |
|
209 |
if (this.options.visibleChoices) { |
|
210 |
var scroll = document.getScroll(), |
|
211 |
size = document.getSize(), |
|
212 |
coords = this.choices.getCoordinates(); |
|
213 |
if (coords.right > scroll.x + size.x) scroll.x = coords.right - size.x; |
|
214 |
if (coords.bottom > scroll.y + size.y) scroll.y = coords.bottom - size.y; |
|
215 |
window.scrollTo(Math.min(scroll.x, coords.left), Math.min(scroll.y, coords.top)); |
|
216 |
} |
|
217 |
}, |
|
218 |
|
|
219 |
hideChoices: function(clear) { |
|
220 |
if (clear) { |
|
221 |
var value = this.element.value; |
|
222 |
if (this.options.forceSelect) value = this.opted; |
|
223 |
if (this.options.autoTrim) { |
|
224 |
value = value.split(this.options.separatorSplit).filter($arguments(0)).join(this.options.separator); |
|
225 |
} |
|
226 |
this.observer.setValue(value); |
|
227 |
} |
|
228 |
if (!this.visible) return; |
|
229 |
this.visible = false; |
|
230 |
if (this.selected) this.selected.removeClass('autocompleter-selected'); |
|
231 |
this.observer.clear(); |
|
232 |
var hide = function(){ |
|
233 |
this.choices.setStyle('display', 'none'); |
|
234 |
this.fix.hide(); |
|
235 |
}.bind(this); |
|
236 |
if (this.fx) this.fx.start(0).chain(hide); |
|
237 |
else hide(); |
|
238 |
this.fireEvent('onHide', [this.element, this.choices]); |
|
239 |
}, |
|
240 |
|
|
241 |
prefetch: function() { |
|
242 |
var value = this.element.value, query = value; |
|
243 |
if (this.options.multiple) { |
|
244 |
var split = this.options.separatorSplit; |
|
245 |
var values = value.split(split); |
|
246 |
var index = this.element.getSelectedRange().start; |
|
247 |
var toIndex = value.substr(0, index).split(split); |
|
248 |
var last = toIndex.length - 1; |
|
249 |
index -= toIndex[last].length; |
|
250 |
query = values[last]; |
|
251 |
} |
|
252 |
if (query.length < this.options.minLength) { |
|
253 |
this.hideChoices(); |
|
254 |
} else { |
|
255 |
if (query === this.queryValue || (this.visible && query == this.selectedValue)) { |
|
256 |
if (this.visible) return false; |
|
257 |
this.showChoices(); |
|
258 |
} else { |
|
259 |
this.queryValue = query; |
|
260 |
this.queryIndex = index; |
|
261 |
if (!this.fetchCached()) this.query(); |
|
262 |
} |
|
263 |
} |
|
264 |
return true; |
|
265 |
}, |
|
266 |
|
|
267 |
fetchCached: function() { |
|
268 |
return false; |
|
269 |
if (!this.options.cache |
|
270 |
|| !this.cached |
|
271 |
|| !this.cached.length |
|
272 |
|| this.cached.length >= this.options.maxChoices |
|
273 |
|| this.queryValue) return false; |
|
274 |
this.update(this.filter(this.cached)); |
|
275 |
return true; |
|
276 |
}, |
|
277 |
|
|
278 |
update: function(tokens) { |
|
279 |
this.choices.empty(); |
|
280 |
this.cached = tokens; |
|
281 |
var type = tokens && $type(tokens); |
|
282 |
if (!type || (type == 'array' && !tokens.length) || (type == 'hash' && !tokens.getLength())) { |
|
283 |
(this.options.emptyChoices || this.hideChoices).call(this); |
|
284 |
} else { |
|
285 |
if (this.options.maxChoices < tokens.length && !this.options.overflow) tokens.length = this.options.maxChoices; |
|
286 |
tokens.each(this.options.injectChoice || function(token){ |
|
287 |
var choice = new Element('li', {'html': this.markQueryValue(token)}); |
|
288 |
choice.inputValue = token; |
|
289 |
this.addChoiceEvents(choice).inject(this.choices); |
|
290 |
}, this); |
|
291 |
this.showChoices(); |
|
292 |
} |
|
293 |
}, |
|
294 |
|
|
295 |
choiceOver: function(choice, selection) { |
|
296 |
if (!choice || choice == this.selected) return; |
|
297 |
if (this.selected) this.selected.removeClass('autocompleter-selected'); |
|
298 |
this.selected = choice.addClass('autocompleter-selected'); |
|
299 |
this.fireEvent('onSelect', [this.element, this.selected, selection]); |
|
300 |
if (!this.selectMode) this.opted = this.element.value; |
|
301 |
if (!selection) return; |
|
302 |
this.selectedValue = this.selected.inputValue; |
|
303 |
if (this.overflown) { |
|
304 |
var coords = this.selected.getCoordinates(this.choices), margin = this.options.overflowMargin, |
|
305 |
top = this.choices.scrollTop, height = this.choices.offsetHeight, bottom = top + height; |
|
306 |
if (coords.top - margin < top && top) this.choices.scrollTop = Math.max(coords.top - margin, 0); |
|
307 |
else if (coords.bottom + margin > bottom) this.choices.scrollTop = Math.min(coords.bottom - height + margin, bottom); |
|
308 |
} |
|
309 |
if (this.selectMode) this.setSelection(); |
|
310 |
}, |
|
311 |
|
|
312 |
choiceSelect: function(choice) { |
|
313 |
if (choice) this.choiceOver(choice); |
|
314 |
this.setSelection(true); |
|
315 |
this.queryValue = false; |
|
316 |
this.hideChoices(); |
|
317 |
}, |
|
318 |
|
|
319 |
filter: function(tokens) { |
|
320 |
return (tokens || this.tokens).filter(function(token) { |
|
321 |
return this.test(token); |
|
322 |
}, new RegExp(((this.options.filterSubset) ? '' : '^') + this.queryValue.escapeRegExp(), (this.options.filterCase) ? '' : 'i')); |
|
323 |
}, |
|
324 |
|
|
325 |
/** |
|
326 |
* markQueryValue |
|
327 |
* |
|
328 |
* Marks the queried word in the given string with <span class="autocompleter-queried">*</span> |
|
329 |
* Call this i.e. from your custom parseChoices, same for addChoiceEvents |
|
330 |
* |
|
331 |
* @param {String} Text |
|
332 |
* @return {String} Text |
|
333 |
*/ |
|
334 |
markQueryValue: function(str) { |
|
335 |
return (!this.options.markQuery || !this.queryValue) ? str |
|
336 |
: str.replace(new RegExp('(' + ((this.options.filterSubset) ? '' : '^') + this.queryValue.escapeRegExp() + ')', (this.options.filterCase) ? '' : 'i'), '<span class="autocompleter-queried">$1</span>'); |
|
337 |
}, |
|
338 |
|
|
339 |
/** |
|
340 |
* addChoiceEvents |
|
341 |
* |
|
342 |
* Appends the needed event handlers for a choice-entry to the given element. |
|
343 |
* |
|
344 |
* @param {Element} Choice entry |
|
345 |
* @return {Element} Choice entry |
|
346 |
*/ |
|
347 |
addChoiceEvents: function(el) { |
|
348 |
return el.addEvents({ |
|
349 |
'mouseover': this.choiceOver.bind(this, [el]), |
|
350 |
'click': this.choiceSelect.bind(this, [el]) |
|
351 |
}); |
|
352 |
} |
|
353 |
}); |
|
354 |
|
|
355 |
var OverlayFix = new Class({ |
|
356 |
|
|
357 |
initialize: function(el) { |
|
358 |
if (Browser.Engine.trident) { |
|
359 |
this.element = $(el); |
|
360 |
this.relative = this.element.getOffsetParent(); |
|
361 |
this.fix = new Element('iframe', { |
|
362 |
'frameborder': '0', |
|
363 |
'scrolling': 'no', |
|
364 |
'src': 'javascript:false;', |
|
365 |
'styles': { |
|
366 |
'position': 'absolute', |
|
367 |
'border': 'none', |
|
368 |
'display': 'none', |
|
369 |
'filter': 'progid:DXImageTransform.Microsoft.Alpha(opacity=0)' |
|
370 |
} |
|
371 |
}).inject(this.element, 'after'); |
|
372 |
} |
|
373 |
}, |
|
374 |
|
|
375 |
show: function() { |
|
376 |
if (this.fix) { |
|
377 |
var coords = this.element.getCoordinates(this.relative); |
|
378 |
delete coords.right; |
|
379 |
delete coords.bottom; |
|
380 |
this.fix.setStyles($extend(coords, { |
|
381 |
'display': '', |
|
382 |
'zIndex': (this.element.getStyle('zIndex') || 1) - 1 |
|
383 |
})); |
|
384 |
} |
|
385 |
return this; |
|
386 |
}, |
|
387 |
|
|
388 |
hide: function() { |
|
389 |
if (this.fix) this.fix.setStyle('display', 'none'); |
|
390 |
return this; |
|
391 |
}, |
|
392 |
|
|
393 |
destroy: function() { |
|
394 |
if (this.fix) this.fix = this.fix.destroy(); |
|
395 |
} |
|
396 |
|
|
397 |
}); |
|
398 |
|
|
399 |
Element.implement({ |
|
400 |
|
|
401 |
getSelectedRange: function() { |
|
402 |
if (!Browser.Engine.trident) return {start: this.selectionStart, end: this.selectionEnd}; |
|
403 |
var pos = {start: 0, end: 0}; |
|
404 |
var range = this.getDocument().selection.createRange(); |
|
405 |
if (!range || range.parentElement() != this) return pos; |
|
406 |
var dup = range.duplicate(); |
|
407 |
if (this.type == 'text') { |
|
408 |
pos.start = 0 - dup.moveStart('character', -100000); |
|
409 |
pos.end = pos.start + range.text.length; |
|
410 |
} else { |
|
411 |
var value = this.value; |
|
412 |
var offset = value.length - value.match(/[\n\r]*$/)[0].length; |
|
413 |
dup.moveToElementText(this); |
|
414 |
dup.setEndPoint('StartToEnd', range); |
|
415 |
pos.end = offset - dup.text.length; |
|
416 |
dup.setEndPoint('StartToStart', range); |
|
417 |
pos.start = offset - dup.text.length; |
|
418 |
} |
|
419 |
return pos; |
|
420 |
}, |
|
421 |
|
|
422 |
selectRange: function(start, end) { |
|
423 |
if (Browser.Engine.trident) { |
|
424 |
var diff = this.value.substr(start, end - start).replace(/\r/g, '').length; |
|
425 |
start = this.value.substr(0, start).replace(/\r/g, '').length; |
|
426 |
var range = this.createTextRange(); |
|
427 |
range.collapse(true); |
|
428 |
range.moveEnd('character', start + diff); |
|
429 |
range.moveStart('character', start); |
|
430 |
range.select(); |
|
431 |
} else { |
|
432 |
this.focus(); |
|
433 |
this.setSelectionRange(start, end); |
|
434 |
} |
|
435 |
return this; |
|
436 |
} |
|
437 |
|
|
438 |
}); |
|
439 |
|
|
440 |
/* compatibility */ |
|
441 |
|
|
442 |
Autocompleter.Base = Autocompleter; |
vigiboard/public/js/Autocompleter/Observer.js | ||
---|---|---|
1 |
/** |
|
2 |
* Observer - Observe formelements for changes |
|
3 |
* |
|
4 |
* - Additional code from clientside.cnet.com |
|
5 |
* |
|
6 |
* @version 1.1 |
|
7 |
* |
|
8 |
* @license MIT-style license |
|
9 |
* @author Harald Kirschner <mail [at] digitarald.de> |
|
10 |
* @copyright Author |
|
11 |
*/ |
|
12 |
var Observer = new Class({ |
|
13 |
|
|
14 |
Implements: [Options, Events], |
|
15 |
|
|
16 |
options: { |
|
17 |
periodical: false, |
|
18 |
delay: 1000 |
|
19 |
}, |
|
20 |
|
|
21 |
initialize: function(el, onFired, options){ |
|
22 |
this.element = $(el) || $$(el); |
|
23 |
this.addEvent('onFired', onFired); |
|
24 |
this.setOptions(options); |
|
25 |
this.bound = this.changed.bind(this); |
|
26 |
this.resume(); |
|
27 |
}, |
|
28 |
|
|
29 |
changed: function() { |
|
30 |
var value = this.element.get('value'); |
|
31 |
if ($equals(this.value, value)) return; |
|
32 |
this.clear(); |
|
33 |
this.value = value; |
|
34 |
this.timeout = this.onFired.delay(this.options.delay, this); |
|
35 |
}, |
|
36 |
|
|
37 |
setValue: function(value) { |
|
38 |
this.value = value; |
|
39 |
this.element.set('value', value); |
|
40 |
return this.clear(); |
|
41 |
}, |
|
42 |
|
|
43 |
onFired: function() { |
|
44 |
this.fireEvent('onFired', [this.value, this.element]); |
|
45 |
}, |
|
46 |
|
|
47 |
clear: function() { |
|
48 |
$clear(this.timeout || null); |
|
49 |
return this; |
|
50 |
}, |
|
51 |
|
|
52 |
pause: function(){ |
|
53 |
if (this.timer) $clear(this.timer); |
|
54 |
else this.element.removeEvent('keyup', this.bound); |
|
55 |
return this.clear(); |
|
56 |
}, |
|
57 |
|
|
58 |
resume: function(){ |
|
59 |
this.value = this.element.get('value'); |
|
60 |
if (this.options.periodical) this.timer = this.changed.periodical(this.options.periodical, this); |
|
61 |
else this.element.addEvent('keyup', this.bound); |
|
62 |
return this; |
|
63 |
} |
|
64 |
|
|
65 |
}); |
|
66 |
|
|
67 |
var $equals = function(obj1, obj2) { |
|
68 |
return (obj1 == obj2 || JSON.encode(obj1) == JSON.encode(obj2)); |
|
69 |
}; |
vigiboard/public/js/Autocompleter/vigilo.js | ||
---|---|---|
1 |
Autocompleter.Request.VigiloJSON = new Class({ |
|
2 |
|
|
3 |
Extends: Autocompleter.Request, |
|
4 |
|
|
5 |
options: { |
|
6 |
resVar: 'results', |
|
7 |
}, |
|
8 |
|
|
9 |
initialize: function(el, url, options) { |
|
10 |
this.parent(el, options); |
|
11 |
this.request = new Request.JSON($merge({ |
|
12 |
'url': url, |
|
13 |
'link': 'cancel', |
|
14 |
}, this.options.ajaxOptions)).addEvent('onComplete', this.queryResponse.bind(this)); |
|
15 |
}, |
|
16 |
|
|
17 |
queryResponse: function(response) { |
|
18 |
this.parent(); |
|
19 |
this.update(response[this.options.resVar]); |
|
20 |
} |
|
21 |
|
|
22 |
}); |
|
23 |
|
vigiboard/public/js/jxlib.js | ||
---|---|---|
1 |
/****************************************************************************** |
|
2 |
* MooTools 1.2.2 |
|
3 |
* Copyright (c) 2006-2007 [Valerio Proietti](http://mad4milk.net/). |
|
4 |
* MooTools is distributed under an MIT-style license. |
|
5 |
****************************************************************************** |
|
6 |
* reset.css - Copyright (c) 2006, Yahoo! Inc. All rights reserved. |
|
7 |
* Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt |
|
8 |
****************************************************************************** |
|
9 |
* Jx UI Library, 2.0.1 |
|
10 |
* Copyright (c) 2006-2008, DM Solutions Group Inc. All rights reserved. |
|
11 |
* |
|
12 |
* Permission is hereby granted, free of charge, to any person obtaining a |
|
13 |
* copy of this software and associated documentation files (the "Software"), |
|
14 |
* to deal in the Software without restriction, including without limitation |
|
15 |
* the rights to use, copy, modify, merge, publish, distribute, sublicense, |
|
16 |
* and/or sell copies of the Software, and to permit persons to whom the |
|
17 |
* Software is furnished to do so, subject to the following conditions: |
|
18 |
* |
|
19 |
* The above copyright notice and this permission notice shall be included |
|
20 |
* in all copies or substantial portions of the Software. |
|
21 |
* |
|
22 |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
23 |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
24 |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
|
25 |
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
26 |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
|
27 |
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|
28 |
* DEALINGS IN THE SOFTWARE. |
|
29 |
*****************************************************************************/ |
|
30 |
// $Id: common.js 423 2009-05-12 12:37:56Z pagameba $ |
|
31 |
/** |
|
32 |
* Class: Jx |
|
33 |
* Jx is a global singleton object that contains the entire Jx library |
|
34 |
* within it. All Jx functions, attributes and classes are accessed |
|
35 |
* through the global Jx object. Jx should not create any other |
|
36 |
* global variables, if you discover that it does then please report |
|
37 |
* it as a bug |
|
38 |
* |
|
39 |
* License: |
|
40 |
* Copyright (c) 2008, DM Solutions Group Inc. |
|
41 |
* |
|
42 |
* This file is licensed under an MIT style license |
|
43 |
*/ |
|
44 |
|
|
45 |
/* firebug console supressor for IE/Safari/Opera */ |
|
46 |
window.addEvent('load', function() { |
|
47 |
if (!("console" in window) || !("firebug" in window.console)) { |
|
48 |
var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", |
|
49 |
"group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"]; |
|
50 |
|
|
51 |
window.console = {}; |
|
52 |
for (var i = 0; i < names.length; ++i) { |
|
53 |
window.console[names[i]] = function() {}; |
|
54 |
} |
|
55 |
} |
|
56 |
}); |
|
57 |
/* inspired by extjs, apparently removes css image flicker and related problems in IE 6 */ |
|
58 |
/* This is already done in mootools Source/Core/Browser.js KASI*/ |
|
59 |
/* |
|
60 |
(function() { |
|
61 |
var ua = navigator.userAgent.toLowerCase(); |
|
62 |
var isIE = ua.indexOf("msie") > -1, |
|
63 |
isIE7 = ua.indexOf("msie 7") > -1; |
|
64 |
if(isIE && !isIE7) { |
|
65 |
try { |
|
66 |
document.execCommand("BackgroundImageCache", false, true); |
|
67 |
} catch(e) {} |
|
68 |
} |
|
69 |
})(); |
|
70 |
*/ |
|
71 |
Class.Mutators.Family = function(self,name) { |
|
72 |
if ($defined(name)){ |
|
73 |
self.$family = {'name': name}; |
|
74 |
$[name] = $.object; |
|
75 |
return self; |
|
76 |
} |
|
77 |
else { |
|
78 |
this.implement('$family',{'name':self}); |
|
79 |
} |
|
80 |
}; |
|
81 |
|
|
82 |
/* Setup global namespace |
|
83 |
* If jxcore is loaded by jx.js, then the namespace and baseURL are |
|
84 |
* already established |
|
85 |
*/ |
|
86 |
if (typeof Jx == 'undefined') { |
|
87 |
var Jx = {}; |
|
88 |
(function() { |
|
89 |
var aScripts = document.getElementsByTagName('SCRIPT'); |
|
90 |
for (var i=0; i<aScripts.length; i++) { |
|
91 |
var s = aScripts[i].src; |
|
92 |
var matches = /(.*[jx|js|lib])\/jxlib(.*)/.exec(s); |
|
93 |
if (matches && matches[0]) { |
|
94 |
/** |
|
95 |
* Property: {String} baseURL |
|
96 |
* This is the URL that Jx was loaded from, it is |
|
97 |
* automatically calculated from the script tag |
|
98 |
* src property that included Jx. |
|
99 |
* |
|
100 |
* Note that this assumes that you are loading Jx |
|
101 |
* from a js/ or lib/ folder in parallel to the |
|
102 |
* images/ folder that contains the various images |
|
103 |
* needed by Jx components. If you have a different |
|
104 |
* folder structure, you can define Jx's base |
|
105 |
* by including the following before including |
|
106 |
* the jxlib javascript file: |
|
107 |
* |
|
108 |
* (code) |
|
109 |
* Jx = { |
|
110 |
* baseURL: 'some/path' |
|
111 |
* } |
|
112 |
* (end) |
|
113 |
*/ |
|
114 |
Jx.aPixel = document.createElement('img', {alt:'',title:''}); |
|
115 |
Jx.aPixel.src = matches[1]+'/a_pixel.png'; |
|
116 |
Jx.baseURL = Jx.aPixel.src.substring(0, |
|
117 |
Jx.aPixel.src.indexOf('a_pixel.png')); |
|
118 |
|
|
119 |
} |
|
120 |
} |
|
121 |
/** |
|
122 |
* Determine if we're running in Adobe AIR. If so, determine which sandbox we're in |
|
123 |
*/ |
|
124 |
var src = aScripts[0].src; |
|
125 |
if (src.contains('app:')){ |
|
126 |
Jx.isAir = true; |
|
127 |
} else { |
|
128 |
Jx.isAir = false; |
|
129 |
} |
|
130 |
})(); |
|
131 |
} |
|
132 |
|
|
133 |
/** |
|
134 |
* Method: applyPNGFilter |
|
135 |
* |
|
136 |
* Static method that applies the PNG Filter Hack for IE browsers |
|
137 |
* when showing 24bit PNG's. Used automatically for img tags with |
|
138 |
* a class of png24. |
|
139 |
* |
|
140 |
* The filter is applied using a nifty feature of IE that allows javascript to |
|
141 |
* be executed as part of a CSS style rule - this ensures that the hack only |
|
142 |
* gets applied on IE browsers. |
|
143 |
* |
|
144 |
* The CSS that triggers this hack is only in the ie6.css files of the various |
|
145 |
* themes. |
|
146 |
* |
|
147 |
* Parameters: |
|
148 |
* object {Object} the object (img) to which the filter needs to be applied. |
|
149 |
*/ |
|
150 |
Jx.applyPNGFilter = function(o) { |
|
151 |
var t=Jx.aPixel.src; |
|
152 |
if( o.src != t ) { |
|
153 |
var s=o.src; |
|
154 |
o.src = t; |
|
155 |
o.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+s+"',sizingMethod='scale')"; |
|
156 |
} |
|
157 |
}; |
|
158 |
|
|
159 |
Jx.imgQueue = []; //The queue of images to be loaded |
|
160 |
Jx.imgLoaded = {}; //a hash table of images that have been loaded and cached |
|
161 |
Jx.imagesLoading = 0; //counter for number of concurrent image loads |
|
162 |
|
|
163 |
/** |
|
164 |
* Method: addToImgQueue |
|
165 |
* Request that an image be set to a DOM IMG element src attribute. This puts |
|
166 |
* the image into a queue and there are private methods to manage that queue |
|
167 |
* and limit image loading to 2 at a time. |
|
168 |
* |
|
169 |
* Parameters: |
|
170 |
* obj - {Object} an object containing an element and src |
|
171 |
* property, where element is the element to update and src |
|
172 |
* is the url to the image. |
|
173 |
*/ |
|
174 |
Jx.addToImgQueue = function(obj) { |
|
175 |
if (Jx.imgLoaded[obj.src]) { |
|
176 |
//if this image was already requested (i.e. it's in cache) just set it directly |
|
177 |
obj.element.src = obj.src; |
|
178 |
} else { |
|
179 |
//otherwise stick it in the queue |
|
180 |
Jx.imgQueue.push(obj); |
|
181 |
Jx.imgLoaded[obj.src] = true; |
|
182 |
} |
|
183 |
//start the queue management process |
|
184 |
Jx.checkImgQueue(); |
|
185 |
}; |
|
186 |
|
|
187 |
/** |
|
188 |
* Method: checkImgQueue |
|
189 |
* |
|
190 |
* An internal method that ensures no more than 2 images are loading at a time. |
|
191 |
*/ |
|
192 |
Jx.checkImgQueue = function() { |
|
193 |
while (Jx.imagesLoading < 2 && Jx.imgQueue.length > 0) { |
|
194 |
Jx.loadNextImg(); |
|
195 |
} |
|
196 |
}; |
|
197 |
|
|
198 |
/** |
|
199 |
* Method: loadNextImg |
|
200 |
* |
|
201 |
* An internal method actually populate the DOM element with the image source. |
|
202 |
*/ |
|
203 |
Jx.loadNextImg = function() { |
|
204 |
var obj = Jx.imgQueue.shift(); |
|
205 |
if (obj) { |
|
206 |
++Jx.imagesLoading; |
|
207 |
obj.element.onload = function(){--Jx.imagesLoading; Jx.checkImgQueue();}; |
|
208 |
obj.element.onerror = function(){--Jx.imagesLoading; Jx.checkImgQueue();}; |
|
209 |
obj.element.src = obj.src; |
|
210 |
} |
|
211 |
}; |
|
212 |
|
|
213 |
/** |
|
214 |
* Method: createIframeShim |
|
215 |
* Creates a new iframe element that is intended to fill a container |
|
216 |
* to mask out other operating system controls (scrollbars, inputs, |
|
217 |
* buttons, etc) when HTML elements are supposed to be above them. |
|
218 |
* |
|
219 |
* Returns: |
|
220 |
* an HTML iframe element that can be inserted into the DOM. |
|
221 |
*/ |
|
222 |
Jx.createIframeShim = function() { |
|
223 |
return new Element('iframe', { |
|
224 |
'class':'jxIframeShim', |
|
225 |
'scrolling':'no', |
|
226 |
'frameborder':0 |
|
227 |
}); |
|
228 |
}; |
|
229 |
/** |
|
230 |
* Method: getNumber |
|
231 |
* safely parse a number and return its integer value. A NaN value |
|
232 |
* returns 0. CSS size values are also parsed correctly. |
|
233 |
* |
|
234 |
* Parameters: |
|
235 |
* n - {Mixed} the string or object to parse. |
|
236 |
* |
|
237 |
* Returns: |
|
238 |
* {Integer} the integer value that the parameter represents |
|
239 |
*/ |
|
240 |
Jx.getNumber = function(n, def) { |
|
241 |
var result = n===null||isNaN(parseInt(n,10))?(def||0):parseInt(n,10); |
|
242 |
return result; |
|
243 |
} |
|
244 |
|
|
245 |
/** |
|
246 |
* Method: getPageDimensions |
|
247 |
* return the dimensions of the browser client area. |
|
248 |
* |
|
249 |
* Returns: |
|
250 |
* {Object} an object containing a width and height property |
|
251 |
* that represent the width and height of the browser client area. |
|
252 |
*/ |
|
253 |
Jx.getPageDimensions = function() { |
|
254 |
return {width: window.getWidth(), height: window.getHeight()}; |
|
255 |
} |
|
256 |
|
|
257 |
/** |
|
258 |
* Class: Element |
|
259 |
* |
|
260 |
* Element is a global object provided by the mootools library. The |
|
261 |
* functions documented here are extensions to the Element object provided |
|
262 |
* by Jx to make cross-browser compatibility easier to achieve. Most of the |
|
263 |
* methods are measurement related. |
|
264 |
* |
|
265 |
* While the code in these methods has been converted to use MooTools methods, |
|
266 |
* there may be better MooTools methods to use to accomplish these things. |
|
267 |
* Ultimately, it would be nice to eliminate most or all of these and find the |
|
268 |
* MooTools equivalent or convince MooTools to add them. |
|
269 |
*/ |
|
270 |
Element.implement({ |
|
271 |
/** |
|
272 |
* Method: getBoxSizing |
|
273 |
* return the box sizing of an element, one of 'content-box' or |
|
274 |
*'border-box'. |
|
275 |
* |
|
276 |
* Parameters: |
|
277 |
* elem - {Object} the element to get the box sizing of. |
|
278 |
* |
|
279 |
* Returns: |
|
280 |
* {String} the box sizing of the element. |
|
281 |
*/ |
|
282 |
getBoxSizing : function() { |
|
283 |
var result = 'content-box'; |
|
284 |
if (Browser.Engine.trident || Browser.Engine.presto) { |
|
285 |
var cm = document["compatMode"]; |
|
286 |
if (cm == "BackCompat" || cm == "QuirksMode") { |
|
287 |
result = 'border-box'; |
|
288 |
} else { |
|
289 |
result = 'content-box'; |
|
290 |
} |
|
291 |
} else { |
|
292 |
if (arguments.length === 0) { |
|
293 |
node = document.documentElement; |
|
294 |
} |
|
295 |
var sizing = this.getStyle("-moz-box-sizing"); |
|
296 |
if (!sizing) { |
|
297 |
sizing = this.getStyle("box-sizing"); |
|
298 |
} |
|
299 |
result = (sizing ? sizing : 'content-box'); |
|
300 |
} |
|
301 |
return result; |
|
302 |
}, |
|
303 |
/** |
|
304 |
* Method: getContentBoxSize |
|
305 |
* return the size of the content area of an element. This is the size of |
|
306 |
* the element less margins, padding, and borders. |
|
307 |
* |
|
308 |
* Parameters: |
|
309 |
* elem - {Object} the element to get the content size of. |
|
310 |
* |
|
311 |
* Returns: |
|
312 |
* {Object} an object with two properties, width and height, that |
|
313 |
* are the size of the content area of the measured element. |
|
314 |
*/ |
|
315 |
getContentBoxSize : function() { |
|
316 |
var w = this.offsetWidth; |
|
317 |
var h = this.offsetHeight; |
|
318 |
var padding = this.getPaddingSize(); |
|
319 |
var border = this.getBorderSize(); |
|
320 |
w = w - padding.left - padding.right - border.left - border.right; |
|
321 |
h = h - padding.bottom - padding.top - border.bottom - border.top; |
|
322 |
return {width: w, height: h}; |
|
323 |
}, |
|
324 |
/** |
|
325 |
* Method: getBorderBoxSize |
|
326 |
* return the size of the border area of an element. This is the size of |
|
327 |
* the element less margins. |
|
328 |
* |
|
329 |
* Parameters: |
|
330 |
* elem - {Object} the element to get the border sizing of. |
|
331 |
* |
|
332 |
* Returns: |
|
333 |
* {Object} an object with two properties, width and height, that |
|
334 |
* are the size of the border area of the measured element. |
|
335 |
*/ |
|
336 |
getBorderBoxSize: function() { |
|
337 |
var w = this.offsetWidth; |
|
338 |
var h = this.offsetHeight; |
|
339 |
return {width: w, height: h}; |
|
340 |
}, |
|
341 |
|
|
342 |
/** |
|
343 |
* Method: getMarginBoxSize |
|
344 |
* return the size of the margin area of an element. This is the size of |
|
345 |
* the element plus margins. |
|
346 |
* |
|
347 |
* Parameters: |
|
348 |
* elem - {Object} the element to get the margin sizing of. |
|
349 |
* |
|
350 |
* Returns: |
|
351 |
* {Object} an object with two properties, width and height, that |
|
352 |
* are the size of the margin area of the measured element. |
|
353 |
*/ |
|
354 |
getMarginBoxSize: function() { |
|
355 |
var margins = this.getMarginSize(); |
|
356 |
var w = this.offsetWidth + margins.left + margins.right; |
|
357 |
var h = this.offsetHeight + margins.top + margins.bottom; |
|
358 |
return {width: w, height: h}; |
|
359 |
}, |
|
360 |
|
|
361 |
/** |
|
362 |
* Method: setContentBoxSize |
|
363 |
* set either or both of the width and height of an element to |
|
364 |
* the provided size. This function ensures that the content |
|
365 |
* area of the element is the requested size and the resulting |
|
366 |
* size of the element may be larger depending on padding and |
|
367 |
* borders. |
|
368 |
* |
|
369 |
* Parameters: |
|
370 |
* elem - {Object} the element to set the content area of. |
|
371 |
* size - {Object} an object with a width and/or height property that is the size to set |
|
372 |
* the content area of the element to. |
|
373 |
*/ |
|
374 |
setContentBoxSize : function(size) { |
|
375 |
if (this.getBoxSizing() == 'border-box') { |
|
376 |
var padding = this.getPaddingSize(); |
|
377 |
var border = this.getBorderSize(); |
|
378 |
if (typeof size.width != 'undefined') { |
|
379 |
var width = (size.width + padding.left + padding.right + border.left + border.right); |
|
380 |
if (width < 0) { |
|
381 |
width = 0; |
|
382 |
} |
|
383 |
this.style.width = width + 'px'; |
|
384 |
} |
|
385 |
if (typeof size.height != 'undefined') { |
|
386 |
var height = (size.height + padding.top + padding.bottom + border.top + border.bottom); |
|
387 |
if (height < 0) { |
|
388 |
height = 0; |
|
389 |
} |
|
390 |
this.style.height = height + 'px'; |
|
391 |
} |
|
392 |
} else { |
|
393 |
if (typeof size.width != 'undefined') { |
|
394 |
this.style.width = size.width + 'px'; |
|
395 |
} |
|
396 |
if (typeof size.height != 'undefined') { |
|
397 |
this.style.height = size.height + 'px'; |
|
398 |
} |
|
399 |
} |
|
400 |
}, |
|
401 |
/** |
|
402 |
* Method: setBorderBoxSize |
|
403 |
* set either or both of the width and height of an element to |
|
404 |
* the provided size. This function ensures that the border |
|
405 |
* size of the element is the requested size and the resulting |
|
406 |
* content areaof the element may be larger depending on padding and |
|
407 |
* borders. |
|
408 |
* |
|
409 |
* Parameters: |
|
410 |
* elem - {Object} the element to set the border size of. |
|
411 |
* size - {Object} an object with a width and/or height property that is the size to set |
|
412 |
* the content area of the element to. |
|
413 |
*/ |
|
414 |
setBorderBoxSize : function(size) { |
|
415 |
if (this.getBoxSizing() == 'content-box') { |
|
416 |
var padding = this.getPaddingSize(); |
|
417 |
var border = this.getBorderSize(); |
|
418 |
var margin = this.getMarginSize(); |
|
419 |
if (typeof size.width != 'undefined') { |
|
420 |
var width = (size.width - padding.left - padding.right - border.left - border.right - margin.left - margin.right); |
|
421 |
if (width < 0) { |
|
422 |
width = 0; |
|
423 |
} |
|
424 |
this.style.width = width + 'px'; |
|
425 |
} |
|
426 |
if (typeof size.height != 'undefined') { |
|
427 |
var height = (size.height - padding.top - padding.bottom - border.top - border.bottom - margin.top - margin.bottom); |
|
428 |
if (height < 0) { |
|
429 |
height = 0; |
|
430 |
} |
Also available in: Unified diff