diff --git a/controllers/admin.py b/controllers/admin.py index b10b6abdc..3f8cbec1c 100755 --- a/controllers/admin.py +++ b/controllers/admin.py @@ -87,8 +87,6 @@ def user(): # auth_membership = "user_id", # ) - s3_str = s3base.s3_str - list_fields = ["first_name", "last_name", "email", diff --git a/controllers/cap.py b/controllers/cap.py index e6b0692c8..d7dc7250c 100644 --- a/controllers/cap.py +++ b/controllers/cap.py @@ -312,7 +312,6 @@ def prep(r): rows = db(query).select(itable.sender_name, groupby=itable.sender_name) sender_options = {} - from s3 import s3_str for row in rows: sender_name = row.sender_name sender_options[sender_name] = s3_str(T(sender_name)) @@ -1134,6 +1133,7 @@ def prep(r): r.resource.add_filter(FS("is_template") == True) table = r.table + tablename = "cap_alert" if r.representation == "xls": @@ -1490,7 +1490,6 @@ def set_priority_js(): orderby = wptable.name, ) - from s3 import s3_str priorities = [(s3_str(T(r.name)), r.urgency, r.severity, r.certainty, r.color_code)\ for r in rows] @@ -1527,7 +1526,6 @@ def cap_AreaRowOptionsBuilder(alert_id, caller=None): values = [row.id for row in rows] count = len(values) if count: - from s3 import s3_str if count == 1: query_ = (atable.id == values[0]) else: diff --git a/controllers/deploy.py b/controllers/deploy.py index b4f6ae193..a397230ee 100644 --- a/controllers/deploy.py +++ b/controllers/deploy.py @@ -176,6 +176,11 @@ def person(): settings.hrm.use_skills = True settings.search.filter_manager = True + # Use Legacy table for unavailability + s3db.add_components("pr_person", + deploy_unavailability = "person_id", + ) + return s3db.hrm_person_controller(replace_option = None, csv_extra_fields = [ # CSV column headers, so no T() @@ -945,22 +950,24 @@ def postp(r, output): restrict_e = [str(row.id) for row in rows if not row.enabled] restrict_d = [str(row.id) for row in rows if row.enabled] - from s3 import s3_str - s3.actions += [dict(label=s3_str(T("Enable")), - _class="action-btn", - url=URL(args=["[id]", "enable"]), - restrict = restrict_e), - dict(label=s3_str(T("Disable")), - _class="action-btn", - url = URL(args = ["[id]", "disable"]), - restrict = restrict_d), + s3.actions += [{"label": s3_str(T("Enable")), + "_class": "action-btn", + "url": URL(args=["[id]", "enable"]), + "restrict": restrict_e, + }, + {"label": s3_str(T("Disable")), + "_class": "action-btn", + "url": URL(args = ["[id]", "disable"]), + "restrict": restrict_d, + }, ] if not s3task._is_alive(): # No Scheduler Running - s3.actions += [dict(label=s3_str(T("Poll")), - _class="action-btn", - url = URL(args = ["[id]", "poll"]), - restrict = restrict_d) + s3.actions += [{"label": s3_str(T("Poll")), + "_class": "action-btn", + "url": URL(args = ["[id]", "poll"]), + "restrict": restrict_d, + }, ] return output s3.postp = postp diff --git a/controllers/gis.py b/controllers/gis.py index b6af32312..44c6005dd 100644 --- a/controllers/gis.py +++ b/controllers/gis.py @@ -3034,7 +3034,7 @@ def postp(r, output): # Normal Action Buttons s3_action_buttons(r, deletable=False) # Custom Action Buttons - s3.actions += [{"label": s3base.s3_str(T("Show on Map")), + s3.actions += [{"label": s3_str(T("Show on Map")), "_class": "action-btn", "url": URL(f = "index", vars = {"poi": "[id]"}, diff --git a/controllers/hrm.py b/controllers/hrm.py index cc173a277..f092b771e 100644 --- a/controllers/hrm.py +++ b/controllers/hrm.py @@ -772,6 +772,14 @@ def disciplinary_action(): return s3_rest_controller() +# ============================================================================= +# Shifts +# ============================================================================= +def shift(): + """ Shifts Controller """ + + return s3_rest_controller() + # ============================================================================= # Messaging # ============================================================================= diff --git a/controllers/msg.py b/controllers/msg.py index 32b6917a9..59a0cb261 100644 --- a/controllers/msg.py +++ b/controllers/msg.py @@ -56,7 +56,7 @@ def postp(r, output): # Normal Action Buttons s3_action_buttons(r) # Custom Action Buttons - s3.actions += [{"label": s3base.s3_str(T("Mark Sender")), + s3.actions += [{"label": s3_str(T("Mark Sender")), "url": URL(f = "mark_sender", args = ["[id]"], ), @@ -765,7 +765,6 @@ def postp(r, output): restrict_e = [str(row.id) for row in rows if not row.enabled] restrict_d = [str(row.id) for row in rows if row.enabled] - from s3 import s3_str s3.actions += [{"label": s3_str(T("Enable")), "_class": "action-btn", "url": URL(args=["[id]", "enable"]), @@ -829,7 +828,6 @@ def postp(r, output): restrict_e = [str(row.id) for row in rows if not row.enabled] restrict_d = [str(row.id) for row in rows if row.enabled] - from s3 import s3_str s3.actions += [{"label": s3_str(T("Enable")), "_class": "action-btn", "url": URL(args=["[id]", "enable"]), @@ -910,7 +908,6 @@ def postp(r, output): restrict_e = [str(row.id) for row in rows if not row.enabled] restrict_d = [str(row.id) for row in rows if row.enabled] - from s3 import s3_str s3.actions += [{"label": s3_str(T("Enable")), "_class": "action-btn", "url": URL(args=["[id]", "enable"]), @@ -981,7 +978,6 @@ def postp(r, output): restrict_e = [str(row.id) for row in rows if not row.enabled] restrict_d = [str(row.id) for row in rows if row.enabled] - from s3 import s3_str s3.actions += [{"label": s3_str(T("Enable")), "_class": "action-btn", "url": URL(args=["[id]", "enable"]), @@ -1064,7 +1060,6 @@ def postp(r, output): restrict_e = [str(row.id) for row in rows if not row.enabled] restrict_d = [str(row.id) for row in rows if row.enabled] - from s3 import s3_str s3.actions += [{"label": s3_str(T("Subscribe")), "_class": "action-btn", "url": URL(args=["[id]", "enable"]), @@ -1140,7 +1135,6 @@ def postp(r, output): restrict_e = [str(row.id) for row in rows if not row.enabled] restrict_d = [str(row.id) for row in rows if row.enabled] - from s3 import s3_str s3.actions += [{"label": s3_str(T("Enable")), "_class": "action-btn", "url": URL(args=["[id]", "enable"]), @@ -1416,7 +1410,6 @@ def postp(r, output): restrict_e = [str(row.id) for row in rows if not row.enabled] restrict_d = [str(row.id) for row in rows if row.enabled] - from s3 import s3_str s3.actions += [{"label": s3_str(T("Enable")), "_class": "action-btn", "url": URL(args=["[id]", "enable"]), @@ -1612,7 +1605,6 @@ def postp(r, output): restrict_k = [str(record.id) for record in records] # @ToDo: Make these S3Methods rather than additional controllers - from s3 import s3_str s3.actions += [{"label": s3_str(T("Search")), "_class": "action-btn", "url": URL(args=["[id]", "poll"]), @@ -1836,7 +1828,6 @@ def postp(r, output): restrict_e = [str(row.id) for row in rows if not row.enabled] restrict_d = [str(row.id) for row in rows if row.enabled] - from s3 import s3_str s3.actions += [{"label": s3_str(T("Enable")), "_class": "action-btn", "url": URL(args=["[id]", "enable"]), diff --git a/controllers/req.py b/controllers/req.py index eb498a498..c1f3baf85 100644 --- a/controllers/req.py +++ b/controllers/req.py @@ -732,7 +732,6 @@ def postp(r, output): r.next = URL(args=[form_vars.id, "req_skill"]) else: s3_action_buttons(r, deletable =False) - s3_str = s3base.s3_str # Add delete button for those records which are not completed # @ToDo: Handle icons table = r.table @@ -770,7 +769,7 @@ def postp(r, output): #rows = db(query).select(table.id) #restrict = [str(row.id) for row in rows] #s3.actions.append( - # {"label": s3_str(T("View Items")), + ## {"label": s3_str(T("View Items")), # "url": URL(c = "req", # f = "req", # args = ["[id]", "req_item"], diff --git a/controllers/setup.py b/controllers/setup.py index dca6c4b38..1548a1372 100644 --- a/controllers/setup.py +++ b/controllers/setup.py @@ -186,7 +186,6 @@ def postp(r, output): restrict_d = [str(row.id) for row in rows if row.task_id is None] restrict_s = [str(row.id) for row in rows if row.task_id is not None] restrict_c = [str(row.id) for row in rows if row.type in (3, 4)] - s3_str = s3base.s3_str s3.actions += [{"url": URL(c = module, f = "deployment", args = [deployment_id, "instance", "[id]", "deploy"], @@ -278,7 +277,6 @@ def postp(r, output): ) restrict_e = [str(row.server_id) for row in rows if row.enabled is False] restrict_d = [str(row.server_id) for row in rows if row.enabled is True] - s3_str = s3base.s3_str s3.actions += [{"url": URL(args = ["[id]", "enable"]), "_class": "action-btn", "label": s3_str(T("Enable")), @@ -363,7 +361,6 @@ def postp(r, output): ) restrict_e = [str(row.id) for row in rows if not row.enabled] restrict_d = [str(row.id) for row in rows if row.enabled] - s3_str = s3base.s3_str s3.actions += [{"url": URL(args=["[id]", "enable"]), "_class": "action-btn", "label": s3_str(T("Enable")), diff --git a/controllers/sit.py b/controllers/sit.py index 60c18b602..837af7ab6 100644 --- a/controllers/sit.py +++ b/controllers/sit.py @@ -5,7 +5,7 @@ """ module = request.controller -resourcename = request.function +#resourcename = request.function if not settings.has_module(module): raise HTTP(404, body="Module disabled: %s" % module) diff --git a/controllers/stdm.py b/controllers/stdm.py index 07a0c807b..97c7a12eb 100644 --- a/controllers/stdm.py +++ b/controllers/stdm.py @@ -174,10 +174,12 @@ def postp(r, output): # Normal Action Buttons s3_action_buttons(r) # Custom Action Buttons - s3.actions += [dict(label=s3base.s3_str(T("Certificate")), - _class="action-btn", - url=URL(f="tenure", - args=["[id]", "certificate"])), + s3.actions += [{"label": s3_str(T("Certificate")), + "_class": "action-btn", + "url": URL(f = "tenure", + args = ["[id]", "certificate"], + ), + }, ] return output @@ -258,10 +260,12 @@ def postp(r, output): # Normal Action Buttons s3_action_buttons(r) # Custom Action Buttons - s3.actions += [dict(label=s3base.s3_str(T("Certificate")), - _class="action-btn", - url=URL(f="tenure", - args=["[id]", "certificate"])), + s3.actions += [{"label": s3_str(T("Certificate")), + "_class": "action-btn", + "url": URL(f = "tenure", + args = ["[id]", "certificate"], + ), + }, ] return output @@ -279,10 +283,12 @@ def postp(r, output): # Normal Action Buttons s3_action_buttons(r) # Custom Action Buttons - s3.actions += [dict(label=s3base.s3_str(T("Certificate")), - _class="action-btn", - url=URL(f="tenure", - args=["[id]", "certificate"])), + s3.actions += [{"label": s3_str(T("Certificate")), + "_class": "action-btn", + "url": URL(f = "tenure", + args = ["[id]", "certificate"], + ), + }, ] return output diff --git a/controllers/survey.py b/controllers/survey.py index edeb39067..7e11de0ac 100644 --- a/controllers/survey.py +++ b/controllers/survey.py @@ -256,7 +256,7 @@ def postp(r, output): return output["item"] if not r.component and method != "summary": # Replace the Action buttons - s3.actions = [{"label": s3base.s3_str(messages.UPDATE), + s3.actions = [{"label": s3_str(messages.UPDATE), "_class": "action-btn edit", "url": URL(c="survey", f="series", diff --git a/controllers/sync.py b/controllers/sync.py index bc47b7dae..c87347944 100755 --- a/controllers/sync.py +++ b/controllers/sync.py @@ -187,7 +187,7 @@ def prep(r): def postp(r, output): if r.interactive and r.id: if r.component and r.component.alias == "job": - s3.actions = [{"label": s3base.s3_str(T("Reset")), + s3.actions = [{"label": s3_str(T("Reset")), "url": URL(c = "sync", f = "repository", args = [str(r.id), diff --git a/models/00_db.py b/models/00_db.py index 921626fac..c7c983035 100644 --- a/models/00_db.py +++ b/models/00_db.py @@ -124,8 +124,10 @@ # CRUD s3.crud = Storage() -# S3 Custom Validators and Widgets, imported here into the global -# namespace in order to access them without the s3base namespace prefix +# Frequently used S3 utilities, validators and widgets, imported here +# into the global namespace in order to access them without the s3base +# namespace prefix +s3_str = s3base.s3_str s3_action_buttons = s3base.S3CRUD.action_buttons s3_fullname = s3base.s3_fullname s3_redirect_default = s3base.s3_redirect_default diff --git a/models/00_tables.py b/models/00_tables.py index ee1c0ad35..a4a26237c 100644 --- a/models/00_tables.py +++ b/models/00_tables.py @@ -45,13 +45,13 @@ import s3db.po import s3db.police import s3db.pr -import s3db.sit import s3db.proc import s3db.project import s3db.req import s3db.s3 import s3db.security import s3db.setup +import s3db.sit import s3db.stats import s3db.stdm import s3db.supply diff --git a/models/zzz_1st_run.py b/models/zzz_1st_run.py index a5045f403..98c2083d9 100644 --- a/models/zzz_1st_run.py +++ b/models/zzz_1st_run.py @@ -311,7 +311,6 @@ def duration(msg, start): try: info(error) except: - s3_str = s3base.s3_str info("\n".join(s3_str(el) for el in error)) # Check to see if the "SITE_DEFAULT" gis_hierarchy was prepopulated. diff --git a/modules/s3/codecs/card.py b/modules/s3/codecs/card.py index e38f6423a..66e0372b8 100644 --- a/modules/s3/codecs/card.py +++ b/modules/s3/codecs/card.py @@ -47,7 +47,7 @@ from gluon import current, HTTP -from s3compat import StringIO +from s3compat import BytesIO from ..s3codec import S3Codec from ..s3resource import S3Resource from ..s3utils import s3_str @@ -178,7 +178,7 @@ def encode(self, resource, **attr): ) # Build the doc - output_stream = StringIO() + output_stream = BytesIO() doc.build(flowables, output_stream, #canvasmaker=canvas.Canvas, # is default @@ -715,7 +715,7 @@ def draw_image(self, Helper function to draw an image - requires PIL (required for ReportLab image handling anyway) - @param img: the image (filename or StringIO buffer) + @param img: the image (filename or BytesIO buffer) @param x: drawing position @param y: drawing position @param width: the target width of the image (in points) diff --git a/modules/s3/s3data.py b/modules/s3/s3data.py index 44ea057b5..b1dfba6e5 100644 --- a/modules/s3/s3data.py +++ b/modules/s3/s3data.py @@ -329,8 +329,9 @@ def json(self, after the group title. """ + attr_get = attr.get flist = self.colnames - action_col = attr.get("dt_action_col", 0) + action_col = attr_get("dt_action_col", 0) if action_col != 0: if action_col == -1 or action_col >= len(flist): action_col = len(flist) - 1 @@ -338,8 +339,8 @@ def json(self, # Get the details for any bulk actions. If we have at least one bulk # action then a column will be added, either at the start or in the # column identified by dt_bulk_col - bulkActions = attr.get("dt_bulk_actions", None) - bulkCol = attr.get("dt_bulk_col", 0) + bulkActions = attr_get("dt_bulk_actions", None) + bulkCol = attr_get("dt_bulk_col", 0) if bulkActions: if bulkCol > len(flist): bulkCol = len(flist) @@ -352,8 +353,8 @@ def json(self, id, draw, flist, - action_col=action_col, - stringify=stringify, + action_col = action_col, + stringify = stringify, **attr) # ------------------------------------------------------------------------- @@ -648,6 +649,7 @@ def htmlConfig(html, dt_bulk_col: The column in which the checkboxes will appear, by default it will be the column immediately before the first data item + dt_bulk_single: only allow a single row to be selected dt_group: The column(s) that is(are) used to group the data dt_group_totals: The number of record in each group. This will be displayed in parenthesis @@ -745,6 +747,8 @@ def htmlConfig(html, bulkActions = [bulkActions] config.bulkActions = bulkActions config.bulkCol = bulkCol = attr_get("dt_bulk_col", 0) + if attr_get("dt_bulk_single"): + config.bulkSingle = 1 action_col = attr_get("dt_action_col", 0) if bulkActions and bulkCol <= action_col: action_col += 1 @@ -1188,7 +1192,7 @@ def groups(iterable, length): while group: yield group group = list(islice(iterable, length)) - raise StopIteration + return # ============================================================================= class S3DataListLayout(object): diff --git a/modules/s3/s3filter.py b/modules/s3/s3filter.py index 1247e3b7f..338d862b5 100644 --- a/modules/s3/s3filter.py +++ b/modules/s3/s3filter.py @@ -743,6 +743,63 @@ class S3DateFilter(S3RangeFilter): # Untranslated labels for individual input boxes. input_labels = {"ge": "From", "le": "To"} + # ------------------------------------------------------------------------- + def __call__(self, resource, get_vars=None, alias=None): + """ + Entry point for the form builder + - subclassed from S3FilterWidget to handle 'available' selector + + @param resource: the S3Resource to render the widget for + @param get_vars: the GET vars (URL query vars) to prepopulate + the widget + @param alias: the resource alias to use + """ + + self.alias = alias + + # Initialize the widget attributes + self._attr(resource) + + # Extract the URL values to populate the widget + variable = self.variable(resource, get_vars) + + defaults = {} + for k, v in self.values.items(): + if k.startswith("available"): + selector = k + else: + selector = self._prefix(k) + defaults[selector] = v + + if type(variable) is list: + values = Storage() + for k in variable: + if k in defaults: + values[k] = defaults[k] + else: + values[k] = self._values(get_vars, k) + else: + if variable in defaults: + values = defaults[variable] + else: + values = self._values(get_vars, variable) + + # Construct and populate the widget + widget = self.widget(resource, values) + + # Recompute variable in case operator got changed in widget() + if self.alternatives: + variable = self._variable(self.selector, self.operator) + + # Construct the hidden data element + data = self.data_element(variable) + + if type(data) is list: + data.append(widget) + else: + data = [data, widget] + return TAG[""](*data) + # ------------------------------------------------------------------------- def data_element(self, variables): """ diff --git a/modules/s3/s3navigation.py b/modules/s3/s3navigation.py index e2df63ef9..cb90fca7e 100644 --- a/modules/s3/s3navigation.py +++ b/modules/s3/s3navigation.py @@ -1474,10 +1474,9 @@ def render(self, r): # Complete the tab URL with args, deal with "viewing" if component: - if record_id: - args = [record_id, component] - else: - args = [component] + args = [record_id, component] if record_id else [component] + if tab.method: + args.append(tab.method) if "viewing" in _vars: del _vars["viewing"] _href = URL(function, args=args, vars=_vars) @@ -1592,15 +1591,14 @@ def __init__(self, tab): # @todo: use component hook label/plural as fallback for title # (see S3Model.add_components) title, component = tab[:2] + + self.title = title + if component and component.find("/") > 0: function, component = component.split("/", 1) else: function = None - self.title = title - - self.native = False - if function: self.function = function else: @@ -1611,11 +1609,16 @@ def __init__(self, tab): else: self.component = None + self.native = False + self.method = None + if len(tab) > 2: tab_vars = self.vars = Storage(tab[2]) if "native" in tab_vars: self.native = True if tab_vars["native"] else False del tab_vars["native"] + if len(tab) > 3: + self.method = tab[3] else: self.vars = None diff --git a/modules/s3/s3roles.py b/modules/s3/s3roles.py index 00270289d..8cdf5d8cc 100644 --- a/modules/s3/s3roles.py +++ b/modules/s3/s3roles.py @@ -387,9 +387,9 @@ def role_form(self, r, **attr): permissions.readable = permissions.writable = False elif not current.auth.permission.use_cacls: # Security policy does not use configurable permissions - record.permissions = None - permissions.represent = self.policy_hint - permissions.writable = False + if record: + record.permissions = None + permissions.widget = self.policy_hint elif readonly: # Read-only view (dummy) - just hide permissions permissions.readable = permissions.writable = False @@ -482,21 +482,27 @@ def role_form(self, r, **attr): # ------------------------------------------------------------------------- @staticmethod - def policy_hint(value): + def policy_hint(field, value, **attr): """ Show a hint if permissions cannot be edited due to security policy - @param value: ignored (dummy, function is used as Field.represent) + @param field: the Field instance + @param value: the current field value (ignored) + @param attr: DOM attributes for the widget (ignored) """ T = current.T warn = T("The current system configuration uses hard-coded access rules (security policy %(policy)s).") % \ - {"policy": current.deployment_settings.get_security_policy()} + {"policy": current.deployment_settings.get_security_policy()} hint = T("Change to security policy 3 or higher if you want to define permissions for roles.") return DIV(SPAN(warn, _class="rm-fixed"), SPAN(hint, _class="rm-hint"), + INPUT(_type = "hidden", + _name = field.name, + _value= "", + ), ) # ------------------------------------------------------------------------- diff --git a/modules/s3/s3validators.py b/modules/s3/s3validators.py index cd8b4b501..93c7d1228 100644 --- a/modules/s3/s3validators.py +++ b/modules/s3/s3validators.py @@ -1238,16 +1238,17 @@ def __call__(self, value): return (value, None) r = current.request - post_vars = r.post_vars if r.env.request_method == "GET": return (value, None) + post_vars = r.post_vars + # If there's a newly uploaded file, accept it. It'll be processed in # the update form. # NOTE: A FieldStorage with data evaluates as False (odd!) uploaded_image = post_vars.get(self.field_name) - if uploaded_image not in ("", None): + if uploaded_image not in (b"", None): # Py 3.x it's b"", which is equivalent to "" in Py 2.x return (uploaded_image, None) cropped_image = post_vars.get("imagecrop-data") diff --git a/modules/s3/s3widgets.py b/modules/s3/s3widgets.py index 8f259dd03..bd22c4f6b 100644 --- a/modules/s3/s3widgets.py +++ b/modules/s3/s3widgets.py @@ -3644,6 +3644,7 @@ class S3ImageCropWidget(FormWidget): Cropping & Scaling (if necessary) done client-side - currently using JCrop (https://jcrop.com) - @ToDo: Replace with https://blueimp.github.io/jQuery-File-Upload/ ? + Uses the IS_PROCESSED_IMAGE validator @ToDo: Doesn't currently work with Inline Component Forms """ @@ -3670,7 +3671,6 @@ def __call__(self, field, value, download_url=None, **attributes): s3 = current.response.s3 debug = s3.debug scripts = s3.scripts - settings = current.deployment_settings if debug: script = "%s/jquery.color.js" % script_dir @@ -3730,7 +3730,7 @@ def __call__(self, field, value, download_url=None, **attributes): append(canvas) btn_class = "imagecrop-btn button" - if settings.ui.formstyle == "bootstrap": + if current.deployment_settings.ui.formstyle == "bootstrap": btn_class = "imagecrop-btn" buttons = [ A(T("Enable Crop"), diff --git a/modules/s3cfg.py b/modules/s3cfg.py index 6b0bdd58f..b6485c11c 100644 --- a/modules/s3cfg.py +++ b/modules/s3cfg.py @@ -4433,6 +4433,7 @@ def get_hrm_vol_active_tooltip(self): def get_hrm_vol_availability_tab(self): """ + @ToDo: Deprecate Whether to use Availability Tab for Volunteers Options: None @@ -4440,6 +4441,17 @@ def get_hrm_vol_availability_tab(self): """ return self.__lazy("hrm", "vol_availability_tab", default=None) + def get_hrm_unavailability(self): + """ + Whether to use Unavailability for Staff/Volunteers + - shows tab/profile widget + - adds filter + Options: + None + True + """ + return self.__lazy("hrm", "unavailability", default=None) + def get_hrm_vol_experience(self): """ Whether to use Experience for Volunteers &, if so, which table to use @@ -4810,10 +4822,16 @@ def get_org_organisation_type_rheader(self): def get_org_facilities_tab(self): """ - Whether to show a Tab for Facilities + Whether to show a Tab for Facilities on Organisations """ return self.org.get("facilities_tab", True) + def get_org_facility_shifts(self): + """ + Whether to show a Tab for Shifts on Offices & Facilities + """ + return self.org.get("facility_shifts", True) + def get_org_groups(self): """ Whether to support Organisation Groups or not diff --git a/modules/s3db/deploy.py b/modules/s3db/deploy.py index c68937dd9..f126287a9 100644 --- a/modules/s3db/deploy.py +++ b/modules/s3db/deploy.py @@ -402,6 +402,8 @@ def model(self): # Unavailability # - periods where an HR is not available for deployments # + # @ToDo: Replace with pr_unavailability with "use_time": False + # tablename = "deploy_unavailability" define_table(tablename, self.pr_person_id(ondelete="CASCADE"), diff --git a/modules/s3db/hrm.py b/modules/s3db/hrm.py index 6bba725db..9ab07a878 100644 --- a/modules/s3db/hrm.py +++ b/modules/s3db/hrm.py @@ -44,6 +44,7 @@ "S3HRAwardModel", "S3HRDisciplinaryActionModel", "S3HRProgrammeModel", + "S3HRShiftModel", "hrm_AssignMethod", "hrm_HumanResourceRepresent", "hrm_TrainingEventRepresent", @@ -806,8 +807,8 @@ def model(self): if use_code: crud_fields.insert(2, "code") - filter_widgets = hrm_human_resource_filters(resource_type=group, - hrm_type_opts=hrm_type_opts) + filter_widgets = hrm_human_resource_filters(resource_type = group, + hrm_type_opts = hrm_type_opts) report_fields = ["organisation_id", "person_id", @@ -1918,7 +1919,7 @@ def model(self): # ========================================================================= # Availability # - # unused - see vol_availability + # unused - see PRAvailabilityModel # weekdays = {1: T("Monday"), 2: T("Tuesday"), @@ -2082,6 +2083,10 @@ def model(self): group = request.get_vars.get("group", None) + c = current.request.controller + if c not in ("hrm", "vol"): + c = "hrm" + if settings.get_org_autocomplete(): widget = S3OrganisationAutocompleteWidget(default_from_profile=True) else: @@ -2131,11 +2136,12 @@ def model(self): represent )), sortby = "name", - comment=S3PopupLink(f = "skill_type", - label = label_create, - title = label_create, - tooltip = T("Add a new skill type to the catalog."), - ), + comment = S3PopupLink(c = c, + f = "skill_type", + label = label_create, + title = label_create, + tooltip = T("Add a new skill type to the catalog."), + ), ) configure(tablename, @@ -2187,7 +2193,8 @@ def model(self): widget = None tooltip = None - skill_help = S3PopupLink(f = "skill", + skill_help = S3PopupLink(c = c, + f = "skill", label = label_create, tooltip = tooltip, ) @@ -2508,7 +2515,7 @@ def model(self): if is_admin: label_create = crud_strings[tablename].label_create - course_help = S3PopupLink(c = "vol" if group == "volunteer" else "hrm", + course_help = S3PopupLink(c = c, f = "course", label = label_create, ) @@ -2606,12 +2613,12 @@ def model(self): represent )), sortby = "name", - comment=S3PopupLink(c = "hrm", - f = "event_type", - label = label_create, - title = label_create, - tooltip = T("Add a new event type to the catalog."), - ), + comment = S3PopupLink(c = "hrm", + f = "event_type", + label = label_create, + title = label_create, + tooltip = T("Add a new event type to the catalog."), + ), ) configure(tablename, @@ -2743,7 +2750,8 @@ def model(self): #filter_opts=filter_opts, )), sortby = "course_id", - comment = S3PopupLink(f = "training_event", + comment = S3PopupLink(c = c, + f = "training_event", label = ADD_TRAINING_EVENT, ), # Comment this to use a Dropdown & not an Autocomplete @@ -3297,7 +3305,8 @@ def model(self): filter_opts=filter_opts )), sortby = "name", - comment = S3PopupLink(f = "certificate", + comment = S3PopupLink(c = c, + f = "certificate", label = label_create, title = label_create, tooltip = T("Add a new certificate to the catalog."), @@ -5059,6 +5068,306 @@ def model(self): return {"hrm_programme_id": programme_id, } +# ============================================================================= +class S3HRShiftModel(S3Model): + """ + Shifts + """ + + names = (#"hrm_shift_slot", + "hrm_shift", + "hrm_shift_id", + "hrm_human_resource_shift", + ) + + def model(self): + + + + T = current.T + + #configure = self.configure + define_table = self.define_table + set_method = self.set_method + + db = current.db + + # --------------------------------------------------------------------- + # Date Formula + # + #interval_opts = {1: T("Daily"), + # 2: T("Weekly"), + # #3: T("Monthly"), + # #4: T("Yearly"), + # } + + #days_of_week = {0: T("Sunday"), + # 1: T("Monday"), + # 2: T("Tuesday"), + # 3: T("Wednesday"), + # 4: T("Thursday"), + # 5: T("Friday"), + # 6: T("Sunday"), + # } + + #tablename = "hrm_date_formula" + #define_table(tablename, + # Field("name", + # label = T("Name"), + # ), + # # "interval" is a reserved word in MySQL + # Field("date_interval", "integer", + # represent = S3Represent(options = interval_opts), + # #requires = IS_IN_SET(interval_opts), + # ), + # Field("rate", "integer"), # Repeat Frequency + # Field("days_of_week", "list:integer", + # represent = S3Represent(options = days_of_week), + # #requires = IS_IN_SET((0, 1, 2, 3, 4, 5, 6), + # # multiple = True, + # # ), + # ), + # *s3_meta_fields()) + + #configure(tablename, + # deduplicate = S3Duplicate(), + # ) + + # --------------------------------------------------------------------- + # Time Formula + # + #tablename = "hrm_time_formula" + #define_table(tablename, + # Field("name", + # label = T("Name"), + # ), + # Field("all_day", "boolean", + # default = False, + # represent = s3_yes_no_represent, + # ), + # Field("start_time", "time", + # # @ToDo: s3_time reusablefield? + # #widget = + # ), + # Field("end_time", "time", + # #widget = + # ), + # *s3_meta_fields()) + + #configure(tablename, + # deduplicate = S3Duplicate(), + # ) + + # --------------------------------------------------------------------- + # Slots + # + #tablename = "hrm_shift_slot" + #define_table(tablename, + # Field("name", + # label = T("Name"), + # ), + # Field("date_formula_id", "reference hrm_date_formula"), + # Field("time_formula_id", "reference hrm_time_formula"), + # *s3_meta_fields()) + + #configure(tablename, + # deduplicate = S3Duplicate(), + # ) + + #represent = S3Represent(lookup=tablename, translate=True) + #slot_id = S3ReusableField("slot_id", "reference %s" % tablename, + # label = T("Slot"), + # ondelete = "RESTRICT", + # represent = represent, + # requires = IS_EMPTY_OR( + # IS_ONE_OF(db, "hrm_shift_slot.id", + # represent)), + # #comment=S3PopupLink(c = "hrm", + # # f = "shift_slot", + # # label = ADD_SLOT, + # # ), + # ) + + # --------------------------------------------------------------------- + # Shifts + # + tablename = "hrm_shift" + define_table(tablename, + self.hrm_job_title_id(), + self.hrm_skill_id(), + s3_datetime("start_date", + label = T("Start Date"), + set_min = "#hrm_shift_end_date", + ), + s3_datetime("end_date", + label = T("End Date"), + set_max = "#hrm_shift_start_date", + ), + s3_comments(), + *s3_meta_fields()) + + represent = S3Represent(lookup=tablename, fields=["start_date", "end_date"]) + shift_id = S3ReusableField("shift_id", "reference %s" % tablename, + label = T("Shift"), + ondelete = "RESTRICT", + represent = represent, + requires = IS_EMPTY_OR( + IS_ONE_OF(db, "hrm_shift.id", + represent)), + comment = S3PopupLink(c = "hrm", + f = "shift", + label = T("Create Shift"), + ), + ) + + self.add_components(tablename, + hrm_human_resource_shift = {"joinby": "shift_id", + "multiple": False, + } + ) + + crud_form = S3SQLCustomForm("job_title_id", + "skill_id", + "start_date", + "end_date", + "comments", + (T("Assigned"), "human_resource_shift.human_resource_id"), + ) + + list_fields = ["job_title_id", + "skill_id", + "start_date", + "end_date", + "comments", + (T("Assigned"), "human_resource_shift.human_resource_id"), + ] + + self.configure(tablename, + crud_form = crud_form, + list_fields = list_fields, + ) + + # Custom Method to Assign HRs + STAFF = current.deployment_settings.get_hrm_staff_label() + filter_widgets = [S3DateFilter("available", + label = T("Available"), + # Use custom selector to prevent automatic + # parsing (which would result in an error) + selector = "available", + hide_time = False, + ), + #if settings.get_hrm_use_skills(): + S3OptionsFilter("competency.skill_id", + # Better to default (easier to customise/consistency) + #label = T("Skill"), + ), + S3OptionsFilter("job_title_id", + ), + S3OptionsFilter("type", + label = T("Type"), + options = {1: STAFF, + 2: T("Volunteer"), + }, + cols = 2, + hidden = True, + ), + ] + #if settings.get_hrm_multiple_orgs(): + # if settings.get_org_branches(): + # append_filter(S3HierarchyFilter("organisation_id", + # leafonly = False, + # )) + # else: + # append_filter(S3OptionsFilter("organisation_id", + # search = True, + # header = "", + # #hidden = True, + # )) + + list_fields = ["id", + "person_id", + "job_title_id", + "start_date", + (T("Skills"), "person_id$competency.skill_id"), + ] + + set_method("hrm", "shift", + method = "assign", + action = self.hrm_AssignMethod(component = "human_resource_shift", + next_tab = "facility", + filter_widgets = filter_widgets, + list_fields = list_fields, + rheader = hrm_rheader, + )) + + def facility_redirect(r, **attr): + """ + Redirect to the Facility's Shifts tab + """ + + s3db = current.s3db + + # Find the Facility + ltable = s3db.org_site_shift + ftable = s3db.org_facility + query = (ltable.shift_id == r.id) & \ + (ltable.site_id == ftable.site_id) + facility = current.db(query).select(ftable.id, + limitby = (0, 1) + ).first() + redirect(URL(c = "org", + f = "facility", + args = [facility.id, "shift"], + )) + + set_method("hrm", "shift", + method = "facility", + action = facility_redirect) + + # CRUD Strings + current.response.s3.crud_strings[tablename] = Storage( + label_create = T("New Shift"), + title_display = T("Shift Details"), + title_list = T("Shifts"), + title_update = T("Edit Shift"), + #title_upload = T("Import Shift data"), + label_list_button = T("List Shifts"), + msg_record_created = T("Shift added"), + msg_record_modified = T("Shift updated"), + msg_record_deleted = T("Shift deleted"), + msg_list_empty = T("No Shifts defined"), + ) + + # --------------------------------------------------------------------- + # Shifts <> Human Resources + # + tablename = "hrm_human_resource_shift" + define_table(tablename, + shift_id(), + self.hrm_human_resource_id(writable = False), + #s3_comments(), + *s3_meta_fields()) + + # --------------------------------------------------------------------- + # Pass names back to global scope (s3.*) + # + return {"hrm_shift_id": shift_id, + } + + # ------------------------------------------------------------------------- + @staticmethod + def defaults(): + """ + Return safe defaults in case the model has been deactivated. + """ + + dummy = S3ReusableField("dummy_id", "integer", + readable = False, + writable = False) + + return {"hrm_shift_id": lambda **attr: dummy("shift_id"), + } + # ============================================================================= def hrm_programme_hours_month(row): """ @@ -5154,17 +5463,32 @@ class hrm_AssignMethod(S3Method): @ToDo: be able to filter by deployable status for the role """ - def __init__(self, component, next_tab="human_resource", types=None): + # ------------------------------------------------------------------------- + def __init__(self, + component, + next_tab = "human_resource", + types = None, + filter_widgets = None, + list_fields = None, + rheader = None, + ): """ @param component: the Component in which to create records - @param types: a list of types to pick from: Staff, Volunteers, Deployables @param next_tab: the component/method to redirect to after assigning + @param types: a list of types to pick from: Staff, Volunteers, Deployables + @param filter_widgets: a custom list of FilterWidgets to show + @param list_fields: a custom list of Fields to show + @param rheader: an rheader to show """ self.component = component self.next_tab = next_tab self.types = types + self.filter_widgets = filter_widgets + self.list_fields = list_fields + self.rheader = rheader + # ------------------------------------------------------------------------- def apply_method(self, r, **attr): """ Apply method. @@ -5231,41 +5555,70 @@ def apply_method(self, r, **attr): else: selected = [] - # Handle exclusion filter if post_vars.mode == "Exclusive": + # 'Select All' ticked or all rows selected manually if "filterURL" in post_vars: filters = S3URLQuery.parse_url(post_vars.filterURL) else: filters = None query = ~(FS("id").belongs(selected)) - hresource = s3db.resource("hrm_human_resource", - alias = self.component, - filter=query, vars=filters) - rows = hresource.select(["id"], as_rows=True) + resource = s3db.resource("hrm_human_resource", + alias = self.component, + filter = query, + vars = filters) + rows = resource.select(["id"], as_rows=True) selected = [str(row.id) for row in rows] - # Prevent multiple entries in the link table - query = (table.human_resource_id.belongs(selected)) & \ - (table[fkey] == record_id) & \ - (table.deleted != True) - rows = db(query).select(table.id) - rows = dict((row.id, row) for row in rows) - onaccept = component.get_config("create_onaccept", - component.get_config("onaccept", None)) - for human_resource_id in selected: - try: - hr_id = int(human_resource_id.strip()) - except ValueError: - continue - if hr_id not in rows: + if component.multiple: + # Prevent multiple entries in the link table + query = (table.human_resource_id.belongs(selected)) & \ + (table[fkey] == record_id) & \ + (table.deleted != True) + rows = db(query).select(table.id) + rows = dict((row.id, row) for row in rows) + onaccept = component.get_config("create_onaccept", + component.get_config("onaccept", None)) + for human_resource_id in selected: + try: + hr_id = int(human_resource_id.strip()) + except ValueError: + continue + if hr_id not in rows: + link = Storage(human_resource_id = human_resource_id) + link[fkey] = record_id + _id = table.insert(**link) + if onaccept: + link["id"] = _id + form = Storage(vars = link) + onaccept(form) + added += 1 + else: + human_resource_id = selected[0] + exists = db(table[fkey] == record_id).select(table.id, + limitby = (0, 1) + ).first() + if exists: + onaccept = component.get_config("update_onaccept", + component.get_config("onaccept", None)) + + exists.update_record(human_resource_id = human_resource_id) + if onaccept: + link = Storage(id = exists.id, + human_resource_id = human_resource_id) + link[fkey] = record_id + form = Storage(vars = link) + onaccept(form) + else: + onaccept = component.get_config("create_onaccept", + component.get_config("onaccept", None)) link = Storage(human_resource_id = human_resource_id) link[fkey] = record_id _id = table.insert(**link) if onaccept: link["id"] = _id - form = Storage(vars=link) + form = Storage(vars = link) onaccept(form) - added += 1 + added += 1 if r.representation == "popup": # Don't redirect, so we retain popup extension & so close popup @@ -5282,40 +5635,49 @@ def apply_method(self, r, **attr): elif r.http == "GET": + representation = r.representation + # Filter widgets - if controller == "vol": - resource_type = "volunteer" - elif len(types) == 1: - resource_type = "staff" - else: - # Both - resource_type = None - if r.controller == "req": - module = "req" + if self.filter_widgets is not None: + filter_widgets = self.filter_widgets else: - module = controller - filter_widgets = hrm_human_resource_filters(resource_type=resource_type, - module=module) + if controller == "vol": + resource_type = "volunteer" + elif len(types) == 1: + resource_type = "staff" + else: + # Both + resource_type = None + if r.controller == "req": + module = "req" + else: + module = controller + + filter_widgets = hrm_human_resource_filters(resource_type = resource_type, + module = module) # List fields - list_fields = ["id", - "person_id", - "organisation_id", - ] - if len(types) == 2: - list_fields.append((T("Type"), "type")) - list_fields.append("job_title_id") - if settings.get_hrm_use_certificates(): - list_fields.append((T("Certificates"), "person_id$certification.certificate_id")) - if settings.get_hrm_use_skills(): - list_fields.append((T("Skills"), "person_id$competency.skill_id")) - if settings.get_hrm_use_trainings(): - list_fields.append((T("Trainings"), "person_id$training.course_id")) + if self.list_fields is not None: + list_fields = self.list_fields + else: + list_fields = ["id", + "person_id", + "organisation_id", + ] + if len(types) == 2: + list_fields.append((T("Type"), "type")) + list_fields.append("job_title_id") + if settings.get_hrm_use_certificates(): + list_fields.append((T("Certificates"), "person_id$certification.certificate_id")) + if settings.get_hrm_use_skills(): + list_fields.append((T("Skills"), "person_id$competency.skill_id")) + if settings.get_hrm_use_trainings(): + list_fields.append((T("Trainings"), "person_id$training.course_id")) # Data table resource = s3db.resource("hrm_human_resource", - alias=r.component.alias if r.component else None, - vars=get_vars) + alias = r.component.alias if r.component else None, + vars = get_vars) totalrows = resource.count() if "pageLength" in get_vars: display_length = get_vars["pageLength"] @@ -5341,12 +5703,39 @@ def apply_method(self, r, **attr): filter_ = (~db.hrm_human_resource.id.belongs(already)) resource.add_filter(filter_) + ajax_vars = dict(get_vars) + if settings.get_hrm_unavailability(): + apply_availability_filter = False + if get_vars.get("available__ge") or \ + get_vars.get("available__le"): + apply_availability_filter = True + elif representation != "aadata": + available_defaults = response.s3.filter_defaults["hrm_human_resource"]["available"] + if available_defaults: + apply_availability_filter = True + ge = available_defaults.get("ge") + if ge is not None: + ajax_vars["available__ge"] = s3_format_datetime(ge) # Used by dt_ajax_url + get_vars["available__ge"] = s3_format_datetime(ge) # Popped in pr_availability_filter + le = available_defaults.get("le") + if le is not None: + ajax_vars["available__le"] = s3_format_datetime(le) # Used by dt_ajax_url + get_vars["available__le"] = s3_format_datetime(le) # Popped in pr_availability_filter + + if apply_availability_filter: + # Apply availability filter + request = Storage(get_vars = get_vars, + resource = resource, + tablename = "hrm_human_resource", + ) + s3db.pr_availability_filter(request) + dt_id = "datatable" # Bulk actions dt_bulk_actions = [(T("Assign"), "assign")] - if r.representation in ("html", "popup"): + if representation in ("html", "popup"): # Page load resource.configure(deletable = False) @@ -5357,6 +5746,7 @@ def apply_method(self, r, **attr): deletable = False, read_url = profile_url, update_url = profile_url) + response.s3.no_formats = True # Filter form @@ -5364,71 +5754,77 @@ def apply_method(self, r, **attr): # Where to retrieve filtered data from: submit_url_vars = resource.crud._remove_filters(r.get_vars) - filter_submit_url = r.url(vars=submit_url_vars) + filter_submit_url = r.url(vars = submit_url_vars) # Default Filters (before selecting data!) - resource.configure(filter_widgets=filter_widgets) + resource.configure(filter_widgets = filter_widgets) S3FilterForm.apply_filter_defaults(r, resource) # Where to retrieve updated filter options from: - filter_ajax_url = URL(f="human_resource", - args=["filter.options"], - vars={}) + filter_ajax_url = URL(f = "human_resource", + args = ["filter.options"], + vars = {}) get_config = resource.get_config filter_clear = get_config("filter_clear", True) filter_formstyle = get_config("filter_formstyle", None) filter_submit = get_config("filter_submit", True) filter_form = S3FilterForm(filter_widgets, - clear=filter_clear, - formstyle=filter_formstyle, - submit=filter_submit, - ajax=True, - url=filter_submit_url, - ajaxurl=filter_ajax_url, - _class="filter-form", - _id="datatable-filter-form", + clear = filter_clear, + formstyle = filter_formstyle, + submit = filter_submit, + ajax = True, + url = filter_submit_url, + ajaxurl = filter_ajax_url, + _class = "filter-form", + _id = "datatable-filter-form", ) fresource = current.s3db.resource(resource.tablename) alias = r.component.alias if r.component else None ff = filter_form.html(fresource, r.get_vars, - target="datatable", - alias=alias) + target = "datatable", + alias = alias) else: ff = "" # Data table (items) data = resource.select(list_fields, - start=0, - limit=limit, - orderby=orderby, - left=left, - count=True, - represent=True) + start = 0, + limit = limit, + orderby = orderby, + left = left, + count = True, + represent = True) filteredrows = data["numrows"] dt = S3DataTable(data["rfields"], data["rows"]) - items = dt.html(totalrows, filteredrows, dt_id, - dt_ajax_url=r.url(representation="aadata"), - dt_bulk_actions=dt_bulk_actions, - dt_pageLength=display_length, - dt_pagination="true", - dt_searching="false", + dt_ajax_url = r.url(representation = "aadata", + vars = ajax_vars), + dt_bulk_actions = dt_bulk_actions, + dt_bulk_single = not component.multiple, + dt_pageLength = display_length, + dt_pagination = "true", + dt_searching = "false", ) STAFF = settings.get_hrm_staff_label() response.view = "list_filter.html" + rheader = self.rheader + if callable(rheader): + rheader = rheader(r) + output = {"items": items, "title": T("Assign %(staff)s") % {"staff": STAFF}, "list_filter_form": ff, + "rheader": rheader, } - elif r.representation == "aadata": + elif representation == "aadata": # Ajax refresh if "draw" in get_vars: echo = int(get_vars.draw) @@ -5436,20 +5832,20 @@ def apply_method(self, r, **attr): echo = None data = resource.select(list_fields, - start=0, - limit=limit, - orderby=orderby, - left=left, - count=True, - represent=True) + start = 0, + limit = limit, + orderby = orderby, + left = left, + count = True, + represent = True) filteredrows = data["numrows"] dt = S3DataTable(data["rfields"], data["rows"]) - items = dt.json(totalrows, filteredrows, dt_id, echo, - dt_bulk_actions=dt_bulk_actions) + dt_bulk_actions = dt_bulk_actions) + response.headers["Content-Type"] = "application/json" output = items @@ -6679,6 +7075,11 @@ def hrm_rheader(r, tabs=None, profile=False): else: availability_tab = None + if settings.get_hrm_unavailability(): + unavailability_tab = (T("Availability"), "unavailability", {}, "organize") + else: + unavailability_tab = None + description_tab = settings.get_hrm_use_description() or None if description_tab: description_tab = (T(description_tab), "physical_description") @@ -6779,6 +7180,7 @@ def hrm_rheader(r, tabs=None, profile=False): experience_tab2, instructor_tab, teams_tab, + unavailability_tab, #(T("Assets"), "asset"), ] #elif current.session.s3.hrm.mode is not None: @@ -6859,6 +7261,7 @@ def hrm_rheader(r, tabs=None, profile=False): instructor_tab, awards_tab, teams_tab, + unavailability_tab, (T("Assets"), "asset"), ] # Add role manager tab if a user record exists @@ -7024,6 +7427,46 @@ def hrm_rheader(r, tabs=None, profile=False): record.name), ), rheader_tabs) + elif resourcename == "shift": + db = current.db + s3db = current.s3db + record_id = r.id + # Look up Site + stable = s3db.org_site_shift + link = db(stable.shift_id == record_id).select(stable.site_id, + limitby = (0, 1), + ).first() + if link: + site_id = link.site_id + else: + site_id = None + # Look up Assigned + htable = s3db.hrm_human_resource_shift + link = db(htable.shift_id == record_id).select(htable.human_resource_id, + limitby = (0, 1), + ).first() + if link: + human_resource_id = link.human_resource_id + else: + human_resource_id = None + rheader = DIV(TABLE(TR(TH("%s: " % stable.site_id.label), + stable.site_id.represent(site_id), + ), + TR(TH("%s: " % table.skill_id.label), + table.skill_id.represent(record.skill_id), + TH("%s: " % table.job_title_id.label), + table.job_title_id.represent(record.job_title_id), + ), + TR(TH("%s: " % table.start_date.label), + table.start_date.represent(record.start_date), + TH("%s: " % table.end_date.label), + table.end_date.represent(record.end_date), + ), + TR(TH("%s: " % htable.human_resource_id.label), + htable.human_resource_id.represent(human_resource_id), + ), + ), + ) else: rheader = None @@ -7411,6 +7854,9 @@ def prep(r): if deploy: # Apply availability filter s3db.deploy_availability_filter(r) + elif settings.get_hrm_unavailability(): + # Apply availability filter + s3db.pr_availability_filter(r) if s3.rtl: # Ensure that + appears at the beginning of the number @@ -7518,20 +7964,6 @@ def prep(r): #"create_function": "person", #"create_component": "address", } - credentials_widget = {# @ToDo: deployment_setting for Labels - "label": "Sectors", - "label_create": "Add Sector", - "type": "datalist", - "tablename": "hrm_credential", - "filter": FS("person_id") == person_id, - "icon": "tags", - # Default renderer: - #"list_layout": hrm_credential_list_layout, - "create_controller": c, - # Can't do this as this is the HR perspective, not Person perspective - #"create_function": "person", - #"create_component": "credential", - } skills_widget = {"label": "Skills", "label_create": "Add Skill", "type": "datalist", @@ -7580,17 +8012,7 @@ def prep(r): # Default renderer: #"list_layout": s3db.doc_document_list_layout, } - education_widget = {"label": "Education", - "label_create": "Add Education", - "type": "datalist", - "tablename": "pr_education", - "filter": FS("person_id") == person_id, - "icon": "book", - # Can't do this as this is the HR perspective, not Person perspective - #"create_controller": c, - #"create_function": "person", - #"create_component": "education", - } + profile_widgets = [contacts_widget, address_widget, skills_widget, @@ -7599,11 +8021,36 @@ def prep(r): docs_widget, ] + if settings.get_hrm_use_education(): + education_widget = {"label": "Education", + "label_create": "Add Education", + "type": "datalist", + "tablename": "pr_education", + "filter": FS("person_id") == person_id, + "icon": "book", + # Can't do this as this is the HR perspective, not Person perspective + #"create_controller": c, + #"create_function": "person", + #"create_component": "education", + } + profile_widgets.insert(-1, education_widget) + if deploy: + credentials_widget = {# @ToDo: deployment_setting for Labels + "label": "Sectors", + "label_create": "Add Sector", + "type": "datalist", + "tablename": "hrm_credential", + "filter": FS("person_id") == person_id, + "icon": "tags", + # Default renderer: + #"list_layout": hrm_credential_list_layout, + "create_controller": c, + # Can't do this as this is the HR perspective, not Person perspective + #"create_function": "person", + #"create_component": "credential", + } profile_widgets.insert(2, credentials_widget) - if settings.get_hrm_use_education(): - profile_widgets.insert(-1, education_widget) - # Organizer-widget to record periods of unavailability: #profile_widgets.append({"label": "Unavailability", # "type": "organizer", @@ -7616,6 +8063,19 @@ def prep(r): # ), # }) + if settings.get_hrm_unavailability(): + unavailability_widget = {"label": "Unavailability", + "type": "organizer", + "tablename": "pr_unavailability", + "master": "pr_person/%s" % person_id, + "component": "unavailability", + "icon": "calendar", + "url": URL(c="pr", f="person", + args = [person_id, "unavailability"], + ), + } + profile_widgets.insert(-1, unavailability_widget) + # Configure resource s3db.configure("hrm_human_resource", profile_cols = 1, @@ -9961,6 +10421,17 @@ def hrm_human_resource_filters(resource_type = None, hidden = True, )) + if settings.get_hrm_unavailability(): + # Availability Filter + append_filter(S3DateFilter("available", + label = T("Available"), + # Use custom selector to prevent automatic + # parsing (which would result in an error) + selector = "available", + hide_time = False, + hidden = True, + )) + else: # Site filter (staff only) filter_widgets.append(S3OptionsFilter("site_id", diff --git a/modules/s3db/org.py b/modules/s3db/org.py index b46c28e8b..e38578806 100644 --- a/modules/s3db/org.py +++ b/modules/s3db/org.py @@ -45,6 +45,7 @@ "S3SiteModel", "S3SiteDetailsModel", "S3SiteNameModel", + "S3SiteShiftModel", "S3SiteTagModel", "S3SiteLocationModel", "S3FacilityModel", @@ -3307,6 +3308,14 @@ def model(self): req_req = "site_id", req_commit = "site_id", + # Shifts + #org_site_shift = "site_id", + hrm_shift = {"link": "org_site_shift", + "joinby": "site_id", + "key": "shift_id", + "actuate": "replace", + }, + # Procurement Plans proc_plan = "site_id", ) @@ -3704,6 +3713,48 @@ def model(self): # Pass names back to global scope (s3.*) return {} +# ============================================================================= +class S3SiteShiftModel(S3Model): + """ + Site Shifts model + """ + + names = ("org_site_shift", + ) + + def model(self): + + T = current.T + + # --------------------------------------------------------------------- + # Shifts for a Site + # + tablename = "org_site_shift" + self.define_table(tablename, + # Component not instance + self.super_link("site_id", "org_site"), + self.hrm_shift_id(ondelete = "CASCADE"), + s3_comments(), + *s3_meta_fields()) + + # CRUD Strings + #site_label = current.deployment_settings.get_org_site_label() + #current.response.s3.crud_strings[tablename] = Storage( + # label_create = T("New Shift"), + # title_display = T("Shift Details"), + # title_list = T("Shifts"), + # title_update = T("Edit Shift"), + # #title_upload = T("Import Shift data"), + # label_list_button = T("List Shifts"), + # msg_record_created = T("Shift added to %(site_label)s") % {"site_label": site_label}, + # msg_record_modified = T("Shift updated"), + # msg_record_deleted = T("Shift removed from %(site_label)s") % {"site_label": site_label}, + # msg_list_empty = T("No Shifts found for this %(site_label)s") % {"site_label": site_label}, + # ) + + # Pass names back to global scope (s3.*) + return {} + # ============================================================================= class S3SiteTagModel(S3Model): """ @@ -6422,6 +6473,7 @@ def org_rheader(r, tabs=None): ] append_tab = tabs.append + SHIFTS = settings.get_org_facility_shifts() if settings.get_L10n_translate_org_site(): append_tab((T("Local Names"), "name")) if settings.get_org_tags(): @@ -6430,10 +6482,13 @@ def org_rheader(r, tabs=None): (r.controller != "inv" or settings.get_inv_facility_manage_staff()): STAFF = settings.get_hrm_staff_label() append_tab((STAFF, "human_resource")) - permitted = current.auth.s3_has_permission - if permitted("update", tablename, r.id) and \ - permitted("create", "hrm_human_resource_site"): - append_tab((T("Assign %(staff)s") % {"staff": STAFF}, "assign")) + if not SHIFTS: + permitted = current.auth.s3_has_permission + if permitted("update", tablename, r.id) and \ + permitted("create", "hrm_human_resource_site"): + append_tab((T("Assign %(staff)s") % {"staff": STAFF}, "assign")) + if SHIFTS: + append_tab((T("Shifts"), "shift")) if settings.has_module("inv"): tabs = tabs + s3db.inv_tabs(r) if settings.get_org_needs_tab(): @@ -7124,10 +7179,23 @@ def prep(r): s3.prep = prep def postp(r, output): - record = r.record - if r.representation == "plain" and record: + if r.interactive and r.component_name == "shift": + # Normal Action Buttons + S3CRUD.action_buttons(r) + # Custom Action Buttons + s3.actions += [{"label": s3_str(current.T("Assign")), + "url": URL(c = "hrm", + f = "shift", + args = ["[id]", "assign"], + ), + "_class": "action-btn", + }, + ] + + elif r.representation == "plain" and r.record: # Custom Map Popup T = current.T + record = r.record output = TABLE() append = output.append # Edit button diff --git a/modules/s3db/pr.py b/modules/s3db/pr.py index 01831213c..50fc409e6 100644 --- a/modules/s3db/pr.py +++ b/modules/s3db/pr.py @@ -41,6 +41,7 @@ # Person Components "PRAvailabilityModel", + "PRUnavailabilityModel", "PRDescriptionModel", "PREducationModel", "PRIdentityModel", @@ -110,6 +111,7 @@ "pr_image_modify", # Other functions + "pr_availability_filter", "pr_import_prep", # Data List Default Layouts @@ -1051,7 +1053,6 @@ def model(self): "multiple": False, }, cr_shelter_registration_history = "person_id", - deploy_unavailability = "person_id", project_activity_person = "person_id", supply_distribution_person = "person_id", event_incident = {"link": "event_human_resource", @@ -1094,6 +1095,7 @@ def model(self): # Appraisals hrm_appraisal = "person_id", # Availability + pr_unavailability = "person_id", pr_person_availability = {"name": "availability", "joinby": "person_id", # Will need tochange in future @@ -4853,6 +4855,64 @@ def model(self): # return {} +# ============================================================================= +class PRUnavailabilityModel(S3Model): + """ + Allow people to mark times when they are unavailable + - this is generally easier for longer-term volunteers than marking times + when they are available + - usually defined using the Organiser method + """ + + names = ("pr_unavailability", + ) + + def model(self): + + T = current.T + + tablename = "pr_unavailability" + self.define_table(tablename, + self.pr_person_id(ondelete = "CASCADE"), + s3_datetime("start_date", + label = T("Start Date"), + set_min = "#pr_unavailability_end_date", + ), + s3_datetime("end_date", + label = T("End Date"), + set_max = "#pr_unavailability_start_date", + ), + s3_comments(), + *s3_meta_fields()) + + self.configure(tablename, + organize = {"start": "start_date", + "end": "end_date", + "title": "comments", + "description": ["start_date", + "end_date", + ], + }, + ) + + # CRUD Strings + current.response.s3.crud_strings[tablename] = Storage( + label_create = T("Add Period of Unavailability"), + title_display = T("Unavailability"), + title_list = T("Periods of Unavailability"), + title_update = T("Edit Unavailability"), + label_list_button = T("List Periods of Unavailability"), + label_delete_button = T("Delete Unavailability"), + msg_record_created = T("Unavailability added"), + msg_record_modified = T("Unavailability updated"), + msg_record_deleted = T("Unavailability deleted"), + msg_list_empty = T("No Unavailability currently registered")) + + # --------------------------------------------------------------------- + # Pass names back to global scope (s3.*) + # + return {} + # ============================================================================= class PRDescriptionModel(S3Model): """ @@ -8417,11 +8477,12 @@ def apply_method(self, r, **attr): if selector == "current_user.name": user = current.auth.user if user: - doc_data[key] = s3_format_fullname(fname=user.first_name, - lname=user.last_name, - ) + username = s3_format_fullname(fname = user.first_name, + lname = user.last_name, + ) else: - doc_data[key] = current.T("Unknown User") + username = current.T("Unknown User") + doc_data[key] = s3_unicode(username) else: rfield = rfields.get(prefix(selector)) if rfield: @@ -9627,6 +9688,57 @@ def pr_image_modify(image_file, else: return False +# ============================================================================= +def pr_availability_filter(r): + """ + Filter requested resource (hrm_human_resource or pr_person) for + availability during a selected interval + - uses special filter selector "available" from GET vars + - called from prep of the respective controller + - adds resource filter for r.resource + + @param r: the S3Request + """ + + get_vars = r.get_vars + + # Parse start/end date + calendar = current.calendar + start = get_vars.pop("available__ge", None) + if start: + start = calendar.parse_datetime(start) + end = get_vars.pop("available__le", None) + if end: + end = calendar.parse_datetime(end) + + utable = current.s3db.pr_unavailability + + # Construct query for unavailability + query = (utable.deleted == False) + if start and end: + query &= ((utable.start_date >= start) & (utable.start_date <= end)) | \ + ((utable.end_date >= start) & (utable.end_date <= end)) | \ + ((utable.start_date < start) & (utable.end_date > end)) + elif start: + query &= (utable.end_date >= start) | (utable.start_date >= start) + elif end: + query &= (utable.start_date <= end) | (utable.end_date <= end) + else: + return + + # Get person_ids of unavailability-entries + rows = current.db(query).select(utable.person_id, + groupby = utable.person_id, + ) + if rows: + person_ids = set(row.person_id for row in rows) + + # Filter r.resource for non-match + if r.tablename == "hrm_human_resource": + r.resource.add_filter(~(FS("person_id").belongs(person_ids))) + elif r.tablename == "pr_person": + r.resource.add_filter(~(FS("id").belongs(person_ids))) + # ============================================================================= def pr_import_prep(data): """ diff --git a/modules/s3db/setup.py b/modules/s3db/setup.py index aedef3139..d7c713f49 100644 --- a/modules/s3db/setup.py +++ b/modules/s3db/setup.py @@ -654,21 +654,18 @@ def setup_get_templates(path): join = p.join isdir = p.isdir listdir = os.listdir - templates = [basename(t) for t in listdir(path) \ - if basename(t) not in ("historic", - "locations", - "mobile", - "skeleton", - "skeletontheme", - "__init__.py", - "__init__.pyc", - "000_config.py", - ) - ] + + # All subdirectories in the path that contain a config.py are + # templates - except skeleton/skeletontheme + dirs = next(os.walk(path))[1] + templates = [d for d in dirs + if d[:8] != "skeleton" and + os.path.isfile(os.path.join(path, d, "config.py")) + ] + subtemplates = [] sappend = subtemplates.append - for i in range(0, len(templates)): - template = templates[i] + for template in templates: tpath = join(path, template) for d in listdir(tpath): if isdir(join(tpath, d)): diff --git a/modules/s3db/sit.py b/modules/s3db/sit.py index ba957b868..da682c9c1 100644 --- a/modules/s3db/sit.py +++ b/modules/s3db/sit.py @@ -27,11 +27,13 @@ OTHER DEALINGS IN THE SOFTWARE. """ -__all__ = ("S3SituationModel",) +__all__ = ("S3SituationModel", + ) from gluon import * from gluon.storage import Storage from ..s3 import * +#from s3layouts import S3PopupLink # ============================================================================= class S3SituationModel(S3Model): @@ -57,7 +59,8 @@ def model(self): # --------------------------------------------------------------------- # Situation Super-Entity # - situation_types = Storage(irs_incident = T("Incident"), + situation_types = Storage(# @ToDo: Deprecate + #irs_incident = T("Incident"), rms_req = T("Request"), pr_presence = T("Presence"), ) @@ -145,8 +148,8 @@ def model(self): # --------------------------------------------------------------------- # Pass names back to global scope (s3.*) # - return dict(sit_location = self.sit_location, - ) + return {"sit_location": self.sit_location, + } # --------------------------------------------------------------------- @staticmethod diff --git a/modules/s3db/vol.py b/modules/s3db/vol.py index babc7de41..a60088bdb 100644 --- a/modules/s3db/vol.py +++ b/modules/s3db/vol.py @@ -1327,6 +1327,10 @@ def prep(r): table = r.table table.type.default = 2 + if settings.get_hrm_unavailability(): + # Apply availability filter + s3db.pr_availability_filter(r) + # Configure list_fields if r.representation == "xls": s3db.hrm_xls_list_fields(r, staff=False) @@ -1380,8 +1384,8 @@ def prep(r): # Update filter widgets filter_widgets = \ - s3db.hrm_human_resource_filters(resource_type="volunteer", - hrm_type_opts=s3db.hrm_type_opts) + s3db.hrm_human_resource_filters(resource_type = "volunteer", + hrm_type_opts = s3db.hrm_type_opts) # Reconfigure resource.configure(list_fields = list_fields, diff --git a/modules/s3menus.py b/modules/s3menus.py index d55a9565c..e7bfe2e94 100644 --- a/modules/s3menus.py +++ b/modules/s3menus.py @@ -1247,6 +1247,7 @@ def vol(): show_programmes = lambda i: settings.get_hrm_vol_experience() == "programme" skills = lambda i: settings.get_hrm_use_skills() certificates = lambda i: settings.get_hrm_use_certificates() + departments = lambda i: settings.get_hrm_vol_departments() teams = settings.get_hrm_teams() use_teams = lambda i: teams show_staff = lambda i: settings.get_hrm_show_staff() @@ -1267,13 +1268,13 @@ def vol(): M("Search Members", f="group_membership"), M("Import", f="group_membership", m="import"), ), - M("Department Catalog", f="department")( + M("Department Catalog", f="department", check=departments)( M("Create", m="create"), ), M("Volunteer Role Catalog", f="job_title")( M("Create", m="create"), ), - M("Skill Catalog", f="skill")( + M("Skill Catalog", f="skill", check=skills)( M("Create", m="create"), #M("Skill Provisions", f="skill_provision"), ), @@ -1642,6 +1643,7 @@ def org(): M("Create", m="create"), ), M("Resource Types", f="resource_type", + check=stats, restrict=[ADMIN])( M("Create", m="create"), ), diff --git a/modules/templates/VM/config.py b/modules/templates/VM/config.py index 536dbfcc4..e771ffd2e 100644 --- a/modules/templates/VM/config.py +++ b/modules/templates/VM/config.py @@ -63,6 +63,10 @@ def config(settings): # #settings.security.policy = 7 # Organisation-ACLs + # Options: @ToDo: make these configurable via Setup Wizard + settings.hrm.unavailability = True + settings.org.facility_shifts = True + # ------------------------------------------------------------------------- # Comment/uncomment modules here to disable/enable them # Modules menu is defined in modules/eden/menu.py @@ -239,4 +243,54 @@ def config(settings): #)), ]) + # ----------------------------------------------------------------------------- + def customise_hrm_shift_controller(**attr): + + s3 = current.response.s3 + + # Custom postp + standard_prep = s3.prep + def prep(r): + # Call standard prep + if callable(standard_prep): + result = standard_prep(r) + + if r.method == "assign": + + s3db = current.s3db + f = s3db.org_site_shift.site_id + f.label = T("Site") + f.represent = s3db.org_site_represent + s3db.hrm_human_resource_shift.human_resource_id.label = T("Currently Assigned") + + # Default Filters + tablename = "hrm_human_resource" + from s3 import s3_set_default_filter + record = r.record + job_title_id = record.job_title_id + if job_title_id: + s3_set_default_filter("~.job_title_id", + job_title_id, + tablename = tablename) + skill_id = record.skill_id + if skill_id: + s3_set_default_filter("competency.skill_id", + skill_id, + tablename = tablename) + # NB Availability Filter is custom, + # so needs the pr_availability_filter applying manually to take effect + s3_set_default_filter("available", + {"ge": record.start_date, + "le": record.end_date, + }, + tablename = tablename) + + return True + + s3.prep = prep + + return attr + + settings.customise_hrm_shift_controller = customise_hrm_shift_controller + # END ========================================================================= \ No newline at end of file diff --git a/static/scripts/S3/S3.min.js b/static/scripts/S3/S3.min.js index a71ab8aa3..b2ab76f0f 100644 --- a/static/scripts/S3/S3.min.js +++ b/static/scripts/S3/S3.min.js @@ -1,16 +1,16 @@ /* - 2013-2019 (c) Sahana Software Foundation + 2015-2019 (c) Sahana Software Foundation @license MIT requires jQuery 1.9.1+ requires jQuery UI 1.10 widget factory - - 2015-2019 (c) Sahana Software Foundation + requires jQuery jstree 3.0.3 + 2013-2019 (c) Sahana Software Foundation @license MIT requires jQuery 1.9.1+ requires jQuery UI 1.10 widget factory - requires jQuery jstree 3.0.3 + jQuery UI Core @VERSION http://jqueryui.com @@ -196,10 +196,10 @@ _focusable:function(a){this.focusable=this.focusable.add(a);this._on(a,{focusin: (function(b){"function"===typeof define&&define.amd?define(["jquery"],b):b(jQuery)})(function(b){(function(){function a(b,a,e){return[parseFloat(b[0])*(t.test(b[0])?a/100:1),parseFloat(b[1])*(t.test(b[1])?e/100:1)]}function c(a){var e=a[0];return 9===e.nodeType?{width:a.width(),height:a.height(),offset:{top:0,left:0}}:b.isWindow(e)?{width:a.width(),height:a.height(),offset:{top:a.scrollTop(),left:a.scrollLeft()}}:e.preventDefault?{width:0,height:0,offset:{top:e.pageY,left:e.pageX}}:{width:a.outerWidth(), height:a.outerHeight(),offset:a.offset()}}b.ui=b.ui||{};var f,e,g=Math.max,k=Math.abs,h=Math.round,l=/left|center|right/,n=/top|center|bottom/,m=/[\+\-]\d+(\.[\d]+)?%?/,r=/^\w+/,t=/%$/,p=b.fn.position;b.position={scrollbarWidth:function(){if(void 0!==f)return f;var a=b("
");var e=a.children()[0];b("body").append(a);var c=e.offsetWidth;a.css("overflow","scroll");e=e.offsetWidth; c===e&&(e=a[0].clientWidth);a.remove();return f=c-e},getScrollInfo:function(a){var e=a.isWindow||a.isDocument?"":a.element.css("overflow-x"),c=a.isWindow||a.isDocument?"":a.element.css("overflow-y");e="scroll"===e||"auto"===e&&a.widthe?"left":0h?"top":0g(k(c),k(h))?n.important="horizontal":n.important="vertical";f.using.call(this,b,n)});l.offset(b.extend(C,{using:c}))})};b.ui.position={fit:{left:function(b,a){var e=a.within,c=e.isWindow?e.scrollLeft:e.offset.left,f=e.width,h=b.left-a.collisionPosition.marginLeft;e=c-h;var l=h+a.collisionWidth-f-c; a.collisionWidth>f?0=l?(a=b.left+e+a.collisionWidth-f-c,b.left+=e-a):b.left=0=e?c:e>l?c+f-a.collisionWidth:c:b.left=0f?0=l?(a=b.top+e+a.collisionHeight-f-c,b.top+=e-a):b.top=0=e?c:e>l?c+f-a.collisionHeight:c:b.top=0e){if(a=b.left+g+m+r+a.collisionWidth-f-c,0>a||aa,""]},iso8601Week:function(a){var b=new Date(a.getTime());b.setDate(b.getDate()+4-(b.getDay()||7));a=b.getTime();b.setMonth(0);b.setDate(1);return Math.floor(Math.round((a-b)/864E5)/7)+1},parseDate:function(a,e,c){if(null==a||null==e)throw"Invalid arguments";e="object"===typeof e?e.toString():e+ -"";if(""===e)return null;var f,g=0;var h=(c?c.shortYearCutoff:null)||this._defaults.shortYearCutoff;h="string"!==typeof h?h:(new Date).getFullYear()%100+parseInt(h,10);var k=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort;var l=(c?c.dayNames:null)||this._defaults.dayNames,n=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,v=(c?c.monthNames:null)||this._defaults.monthNames,y=c=-1,u=-1,B=-1,C=!1,D=function(b){(b=f+1c&&(c+=(new Date).getFullYear()-(new Date).getFullYear()%100+(c<=h?0:-100));if(-1c&&(c+=(new Date).getFullYear()-(new Date).getFullYear()%100+(c<=h?0:-100));if(-1b.getYear()%100?"0":"")+b.getYear()%100;break;case "@":y+=b.getTime();break;case "!":y+=1E4*b.getTime()+this._ticksTo1970;break;case "'":k("'")?y+="'":u=!0;break;default:y+=a.charAt(c)}return y},_possibleChars:function(a){var b,e="",c=!1,f=function(e){(e=b+1b.getYear()%100?"0":"")+b.getYear()%100;break;case "@":x+=b.getTime();break;case "!":x+=1E4*b.getTime()+this._ticksTo1970;break;case "'":k("'")?x+="'":u=!0;break;default:x+=a.charAt(c)}return x},_possibleChars:function(a){var b,e="",c=!1,f=function(e){(e=b+1u&&(u+=12,F--);if(J){var E=this._daylightSavingAdjust(new Date(J.getFullYear(),J.getMonth()-y[0]*y[1]+1,J.getDate()));for(E=A&&EE;)u--,0>u&&(u=11,F--)}a.drawMonth=u;a.drawYear=F;E=this._get(a,"prevText");E=v?this.formatDate(E,this._daylightSavingAdjust(new Date(F,u-B,1)),this._getFormatConfig(a)): -E;E=this._canAdjustMonth(a,-1,F,u)?""+E+"":x?"":""+E+"";var W=this._get(a,"nextText");W=v?this.formatDate(W,this._daylightSavingAdjust(new Date(F,u+B,1)),this._getFormatConfig(a)):W; -x=this._canAdjustMonth(a,1,F,u)?""+W+"":x?"":""+W+"";B=this._get(a,"currentText");W=this._get(a,"gotoCurrent")&&a.currentDay?D:g;B=v?this.formatDate(B,W,this._getFormatConfig(a)):B;v= -a.inline?"":"";k=k?"
"+(h?v:"")+(this._isInRange(a,W)?"":"")+(h?"":v)+"
":"";v=parseInt(this._get(a,"firstDay"), -10);v=isNaN(v)?0:v;B=this._get(a,"showWeek");W=this._get(a,"dayNames");var z=this._get(a,"dayNamesMin");var Z=this._get(a,"monthNames");var ca=this._get(a,"monthNamesShort");var ia=this._get(a,"beforeShowDay");var P=this._get(a,"showOtherMonths");var da=this._get(a,"selectOtherMonths");var X=this._getDefaultDate(a);var aa="";G;for(b=0;b"+(/all|left/.test(G)&&0===b?h?x:E:"")+(/all|right/.test(G)&&0===b?h?E:x:"")+this._generateMonthYearHeader(a,u,F,A,J,0"; -var I=B?"":"";for(G=0;7>G;G++){var H=(G+v)%7;I+=""}M+=I+"";I=this._getDaysInMonth(F,u);F===a.selectedYear&&u===a.selectedMonth&&(a.selectedDay=Math.min(a.selectedDay,I));G=(this._getFirstDayOfMonth(F,u)-v+7)%7;I=Math.ceil((G+I)/7);this.maxRows=I=C?this.maxRows>I?this.maxRows:I:I;H=this._daylightSavingAdjust(new Date(F, -u,1-G));for(c=0;c";var R=B?"":"";for(G=0;7>G;G++){var U=ia?ia.apply(a.input?a.input[0]:null,[H]):[!0,""];var Y=(f=H.getMonth()!==u)&&!da||!U[0]||A&&HJ;R+="";H.setDate(H.getDate()+1);H=this._daylightSavingAdjust(H)}M+=R+""}u++;11
"+this._get(a,"weekHeader")+""+z[H]+"
"+this._get(a,"calculateWeek")(H)+""+E+"":B?"":""+E+"";var W=this._get(a,"nextText");W=w?this.formatDate(W,this._daylightSavingAdjust(new Date(F,u+z,1)),this._getFormatConfig(a)):W; +B=this._canAdjustMonth(a,1,F,u)?""+W+"":B?"":""+W+"";z=this._get(a,"currentText");W=this._get(a,"gotoCurrent")&&a.currentDay?D:g;z=w?this.formatDate(z,W,this._getFormatConfig(a)):z;w= +a.inline?"":"";k=k?"
"+(h?w:"")+(this._isInRange(a,W)?"":"")+(h?"":w)+"
":"";w=parseInt(this._get(a,"firstDay"), +10);w=isNaN(w)?0:w;z=this._get(a,"showWeek");W=this._get(a,"dayNames");var y=this._get(a,"dayNamesMin");var Z=this._get(a,"monthNames");var ca=this._get(a,"monthNamesShort");var ia=this._get(a,"beforeShowDay");var P=this._get(a,"showOtherMonths");var da=this._get(a,"selectOtherMonths");var X=this._getDefaultDate(a);var aa="";G;for(b=0;b"+(/all|left/.test(G)&&0===b?h?B:E:"")+(/all|right/.test(G)&&0===b?h?E:B:"")+this._generateMonthYearHeader(a,u,F,A,J,0"; +var I=z?"":"";for(G=0;7>G;G++){var H=(G+w)%7;I+=""}M+=I+"";I=this._getDaysInMonth(F,u);F===a.selectedYear&&u===a.selectedMonth&&(a.selectedDay=Math.min(a.selectedDay,I));G=(this._getFirstDayOfMonth(F,u)-w+7)%7;I=Math.ceil((G+I)/7);this.maxRows=I=C?this.maxRows>I?this.maxRows:I:I;H=this._daylightSavingAdjust(new Date(F, +u,1-G));for(c=0;c";var R=z?"":"";for(G=0;7>G;G++){var U=ia?ia.apply(a.input?a.input[0]:null,[H]):[!0,""];var Y=(f=H.getMonth()!==u)&&!da||!U[0]||A&&HJ;R+="";H.setDate(H.getDate()+1);H=this._daylightSavingAdjust(H)}M+=R+""}u++;11
"+this._get(a,"weekHeader")+""+y[H]+"
"+this._get(a,"calculateWeek")(H)+""+(f&&!P?" ":Y?""+H.getDate()+"":""+H.getDate()+"")+"
"+(C?""+(0":""):"");N+=M}aa+=N}a._keyEvent=!1;return aa+k},_generateMonthYearHeader:function(a,b,e,c,f,g,k,w){var h,m=this._get(a,"changeMonth"),l=this._get(a,"changeYear"),t=this._get(a,"showMonthAfterYear"),r="
", -p="";if(g||!m)p+=""+k[b]+"";else{k=c&&c.getFullYear()===e;var n=f&&f.getFullYear()===e;p+=""}t||(r+=p+(!g&&m&&l?"":" "));if(!a.yearshtml)if(a.yearshtml="",g||!l)r+=""+e+""; -else{w=this._get(a,"yearRange").split(":");var A=(new Date).getFullYear();k=function(a){a=a.match(/c[+\-].*/)?e+parseInt(a.substring(1),10):a.match(/[+\-].*/)?A+parseInt(a,10):parseInt(a,10);return isNaN(a)?A:a};b=k(w[0]);w=Math.max(b,k(w[1]||""));b=c?Math.max(b,c.getFullYear()):b;w=f?Math.min(w,f.getFullYear()):w;for(a.yearshtml+="
"+(C?""+(0":""):"");N+=M}aa+=N}a._keyEvent=!1;return aa+k},_generateMonthYearHeader:function(a,b,e,c,f,g,k,v){var h,m=this._get(a,"changeMonth"),l=this._get(a,"changeYear"),t=this._get(a,"showMonthAfterYear"),r="
", +p="";if(g||!m)p+=""+k[b]+"";else{k=c&&c.getFullYear()===e;var n=f&&f.getFullYear()===e;p+=""}t||(r+=p+(!g&&m&&l?"":" "));if(!a.yearshtml)if(a.yearshtml="",g||!l)r+=""+e+""; +else{v=this._get(a,"yearRange").split(":");var A=(new Date).getFullYear();k=function(a){a=a.match(/c[+\-].*/)?e+parseInt(a.substring(1),10):a.match(/[+\-].*/)?A+parseInt(a,10):parseInt(a,10);return isNaN(a)?A:a};b=k(v[0]);v=Math.max(b,k(v[1]||""));b=c?Math.max(b,c.getFullYear()):b;v=f?Math.min(v,f.getFullYear()):v;for(a.yearshtml+="";r+=a.yearshtml;a.yearshtml=null}r+=this._get(a,"yearSuffix");t&&(r+=(!g&&m&&l?"":" ")+p);return r+"
"},_adjustInstDate:function(a,b,e){var c=a.drawYear+("Y"===e?b:0),f=a.drawMonth+("M"===e?b:0);b=Math.min(a.selectedDay,this._getDaysInMonth(c,f))+("D"===e?b:0);c=this._restrictMinMax(a,this._daylightSavingAdjust(new Date(c,f,b)));a.selectedDay=c.getDate();a.drawMonth=a.selectedMonth=c.getMonth();a.drawYear=a.selectedYear=c.getFullYear();"M"!==e&&"Y"!== e||this._notifyChange(a)},_restrictMinMax:function(a,b){var e=this._getMinMaxDate(a,"min");a=this._getMinMaxDate(a,"max");b=e&&ba?a:b},_notifyChange:function(a){var b=this._get(a,"onChangeMonthYear");b&&b.apply(a.input?a.input[0]:null,[a.selectedYear,a.selectedMonth+1,a])},_getNumberOfMonths:function(a){a=this._get(a,"numberOfMonths");return null==a?[1,1]:"number"===typeof a?[1,a]:a},_getMinMaxDate:function(a,b){return this._determineDate(a,this._get(a,b+"Date"),null)},_getDaysInMonth:function(a, b){return 32-this._daylightSavingAdjust(new Date(a,b,32)).getDate()},_getFirstDayOfMonth:function(a,b){return(new Date(a,b,1)).getDay()},_canAdjustMonth:function(a,b,e,c){var f=this._getNumberOfMonths(a);e=this._daylightSavingAdjust(new Date(e,c+(0>b?b:f[0]*f[1]),1));0>b&&e.setDate(this._getDaysInMonth(e.getFullYear(),e.getMonth()));return this._isInRange(a,e)},_isInRange:function(a,b){var e=this._getMinMaxDate(a,"min"),c=this._getMinMaxDate(a,"max"),f=null,g=null;if(a=this._get(a,"yearRange")){a= @@ -356,10 +356,10 @@ g.options.helper=g.options._helper,g.placeholder&&g.placeholder.remove(),f._refr c,f){c=f.options;var e=!1,g=f.scrollParentNotHidden[0],k=f.document[0];g!==k&&"HTML"!==g.tagName?(c.axis&&"x"===c.axis||(f.overflowOffset.top+g.offsetHeight-a.pageYt+k||mw+k||!b.contains(f.snapElements[e].item.ownerDocument,f.snapElements[e].item))f.snapElements[e].snapping&&f.options.snap.release&&f.options.snap.release.call(f.element,a,b.extend(f._uiHash(),{snapItem:f.snapElements[e].item})),f.snapElements[e].snapping= -!1;else{if("inner"!==g.snapMode){var x=Math.abs(p-m)<=k;var v=Math.abs(w-n)<=k;var y=Math.abs(r-l)<=k;var u=Math.abs(t-h)<=k;x&&(c.position.top=f._convertPositionTo("relative",{top:p-f.helperProportions.height,left:0}).top);v&&(c.position.top=f._convertPositionTo("relative",{top:w,left:0}).top);y&&(c.position.left=f._convertPositionTo("relative",{top:0,left:r-f.helperProportions.width}).left);u&&(c.position.left=f._convertPositionTo("relative",{top:0,left:t}).left)}var B=x||v||y||u;"outer"!==g.snapMode&& -(x=Math.abs(p-n)<=k,v=Math.abs(w-m)<=k,y=Math.abs(r-h)<=k,u=Math.abs(t-l)<=k,x&&(c.position.top=f._convertPositionTo("relative",{top:p,left:0}).top),v&&(c.position.top=f._convertPositionTo("relative",{top:w-f.helperProportions.height,left:0}).top),y&&(c.position.left=f._convertPositionTo("relative",{top:0,left:r}).left),u&&(c.position.left=f._convertPositionTo("relative",{top:0,left:t-f.helperProportions.width}).left));!f.snapElements[e].snapping&&(x||v||y||u||B)&&f.options.snap.snap&&f.options.snap.snap.call(f.element, -a,b.extend(f._uiHash(),{snapItem:f.snapElements[e].item}));f.snapElements[e].snapping=x||v||y||u||B}}}});b.ui.plugin.add("draggable","stack",{start:function(a,c,f){a=b.makeArray(b(f.options.stack)).sort(function(a,e){return(parseInt(b(a).css("zIndex"),10)||0)-(parseInt(b(e).css("zIndex"),10)||0)});if(a.length){var e=parseInt(b(a[0]).css("zIndex"),10)||0;b(a).each(function(a){b(this).css("zIndex",e+a)});this.css("zIndex",e+a.length)}}});b.ui.plugin.add("draggable","zIndex",{start:function(a,c,f){a= +n=c.offset.top,m=n+f.helperProportions.height;for(e=f.snapElements.length-1;0<=e;e--){var r=f.snapElements[e].left-f.margins.left;var t=r+f.snapElements[e].width;var p=f.snapElements[e].top-f.margins.top;var v=p+f.snapElements[e].height;if(lt+k||mv+k||!b.contains(f.snapElements[e].item.ownerDocument,f.snapElements[e].item))f.snapElements[e].snapping&&f.options.snap.release&&f.options.snap.release.call(f.element,a,b.extend(f._uiHash(),{snapItem:f.snapElements[e].item})),f.snapElements[e].snapping= +!1;else{if("inner"!==g.snapMode){var B=Math.abs(p-m)<=k;var w=Math.abs(v-n)<=k;var x=Math.abs(r-l)<=k;var u=Math.abs(t-h)<=k;B&&(c.position.top=f._convertPositionTo("relative",{top:p-f.helperProportions.height,left:0}).top);w&&(c.position.top=f._convertPositionTo("relative",{top:v,left:0}).top);x&&(c.position.left=f._convertPositionTo("relative",{top:0,left:r-f.helperProportions.width}).left);u&&(c.position.left=f._convertPositionTo("relative",{top:0,left:t}).left)}var z=B||w||x||u;"outer"!==g.snapMode&& +(B=Math.abs(p-n)<=k,w=Math.abs(v-m)<=k,x=Math.abs(r-h)<=k,u=Math.abs(t-l)<=k,B&&(c.position.top=f._convertPositionTo("relative",{top:p,left:0}).top),w&&(c.position.top=f._convertPositionTo("relative",{top:v-f.helperProportions.height,left:0}).top),x&&(c.position.left=f._convertPositionTo("relative",{top:0,left:r}).left),u&&(c.position.left=f._convertPositionTo("relative",{top:0,left:t-f.helperProportions.width}).left));!f.snapElements[e].snapping&&(B||w||x||u||z)&&f.options.snap.snap&&f.options.snap.snap.call(f.element, +a,b.extend(f._uiHash(),{snapItem:f.snapElements[e].item}));f.snapElements[e].snapping=B||w||x||u||z}}}});b.ui.plugin.add("draggable","stack",{start:function(a,c,f){a=b.makeArray(b(f.options.stack)).sort(function(a,e){return(parseInt(b(a).css("zIndex"),10)||0)-(parseInt(b(e).css("zIndex"),10)||0)});if(a.length){var e=parseInt(b(a[0]).css("zIndex"),10)||0;b(a).each(function(a){b(this).css("zIndex",e+a)});this.css("zIndex",e+a.length)}}});b.ui.plugin.add("draggable","zIndex",{start:function(a,c,f){a= b(c.helper);f=f.options;a.css("zIndex")&&(f._zIndex=a.css("zIndex"));a.css("zIndex",f.zIndex)},stop:function(a,c,f){a=f.options;a._zIndex&&b(c.helper).css("zIndex",a._zIndex)}});return b.ui.draggable}); (function(b){"function"===typeof define&&define.amd?define(["jquery","./core","./widget","./mouse","./draggable"],b):b(jQuery)})(function(b){b.widget("ui.droppable",{version:"@VERSION",widgetEventPrefix:"drop",options:{accept:"*",activeClass:!1,addClasses:!0,greedy:!1,hoverClass:!1,scope:"default",tolerance:"intersect",activate:null,deactivate:null,drop:null,out:null,over:null},_create:function(){var a,c=this.options,f=c.accept;this.isover=!1;this.isout=!0;this.accept=b.isFunction(f)?f:function(a){return a.is(f)}; this.proportions=function(){if(arguments.length)a=arguments[0];else return a?a:a={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight}};this._addToManager(c.scope);c.addClasses&&this.element.addClass("ui-droppable")},_addToManager:function(a){b.ui.ddmanager.droppables[a]=b.ui.ddmanager.droppables[a]||[];b.ui.ddmanager.droppables[a].push(this)},_splice:function(a){for(var b=0;br,v=c.minHeight&&c.minHeight>t;c.grid=h;x&&(r+=l);v&&(t+=n);p&&(r-=l);w&&(t-=n);if(/^(se|s|e)$/.test(k))a.size.width=r,a.size.height=t;else if(/^(ne)$/.test(k))a.size.width=r,a.size.height=t,a.position.top=g.top-f;else if(/^(sw)$/.test(k))a.size.width=r,a.size.height=t,a.position.left= -g.left-m;else{if(0>=t-n||0>=r-l)var y=a._getPaddingPlusBorderDimensions(this);0r,w=c.minHeight&&c.minHeight>t;c.grid=h;B&&(r+=l);w&&(t+=n);p&&(r-=l);v&&(t-=n);if(/^(se|s|e)$/.test(k))a.size.width=r,a.size.height=t;else if(/^(ne)$/.test(k))a.size.width=r,a.size.height=t,a.position.top=g.top-f;else if(/^(sw)$/.test(k))a.size.width=r,a.size.height=t,a.position.left= +g.left-m;else{if(0>=t-n||0>=r-l)var x=a._getPaddingPlusBorderDimensions(this);0",options:{disabled:null, text:!0,label:null,icons:{primary:null,secondary:null}},_create:function(){this.element.closest("form").unbind("reset"+this.eventNamespace).bind("reset"+this.eventNamespace,c);"boolean"!==typeof this.options.disabled?this.options.disabled=!!this.element.prop("disabled"):this.element.prop("disabled",this.options.disabled);this._determineButtonType();this.hasTitle=!!this.buttonElement.attr("title");var e=this,g=this.options,k="checkbox"===this.type||"radio"===this.type,h=k?"":"ui-state-active";null=== g.label&&(g.label="input"===this.type?this.buttonElement.val():this.buttonElement.html());this._hoverable(this.buttonElement);this.buttonElement.addClass("ui-button ui-widget ui-state-default ui-corner-all").attr("role","button").bind("mouseenter"+this.eventNamespace,function(){g.disabled||this===a&&b(this).addClass("ui-state-active")}).bind("mouseleave"+this.eventNamespace,function(){g.disabled||b(this).removeClass(h)}).bind("click"+this.eventNamespace,function(a){g.disabled&&(a.preventDefault(), @@ -515,12 +515,12 @@ e&&(e=c);a=a.parent()}return e},_refreshTimepicker:function(a){(a=this._getInst( b.proxy(b.fgtimepicker.selectMinutes,this)).bind("dblclick",{fromDoubleClick:!0},b.proxy(b.fgtimepicker.selectMinutes,this)).end().find(".ui-timepicker-hour-cell").unbind().bind("click",{fromDoubleClick:!1},b.proxy(b.fgtimepicker.selectHours,this)).bind("dblclick",{fromDoubleClick:!0},b.proxy(b.fgtimepicker.selectHours,this)).end().find(".ui-timepicker td a").unbind().bind("mouseout",function(){b(this).removeClass("ui-state-hover");-1!=this.className.indexOf("ui-timepicker-prev")&&b(this).removeClass("ui-timepicker-prev-hover"); -1!=this.className.indexOf("ui-timepicker-next")&&b(this).removeClass("ui-timepicker-next-hover")}).bind("mouseover",function(){c._isDisabledTimepicker(a.inline?a.tpDiv.parent()[0]:a.input[0])||(b(this).parents(".ui-timepicker-calendar").find("a").removeClass("ui-state-hover"),b(this).addClass("ui-state-hover"),-1!=this.className.indexOf("ui-timepicker-prev")&&b(this).addClass("ui-timepicker-prev-hover"),-1!=this.className.indexOf("ui-timepicker-next")&&b(this).addClass("ui-timepicker-next-hover"))}).end().find("."+ this._dayOverClass+" a").trigger("mouseover").end().find(".ui-timepicker-now").bind("click",function(a){b.fgtimepicker.selectNow(a)}).end().find(".ui-timepicker-deselect").bind("click",function(a){b.fgtimepicker.deselectTime(a)}).end().find(".ui-timepicker-close").bind("click",function(a){b.fgtimepicker._hideTimepicker()}).end()},_generateHTML:function(a){var b,e,c=1==this._get(a,"showPeriod"),f=1==this._get(a,"showPeriodLabels"),n=1==this._get(a,"showLeadingZero");var m=1==this._get(a,"showHours"); -var r=1==this._get(a,"showMinutes"),t=this._get(a,"amPmText"),p=this._get(a,"rows"),w=0,x=0,v=e=0,y=0,u=0,B=[],C=this._get(a,"hours"),D=0;var A=this._get(a,"hourText");var J=this._get(a,"showCloseButton"),F=this._get(a,"closeButtonText"),E=this._get(a,"showNowButton"),W=this._get(a,"nowButtonText"),z=this._get(a,"showDeselectButton"),Z=this._get(a,"deselectButtonText"),ca=J||E||z;for(b=C.starts;b<=C.ends;b++)B.push(b);b=Math.ceil(B.length/p);if(f){for(D=0;DB[D]?e++:v++;D=0;w=Math.floor(e/ -B.length*p);x=Math.floor(v/B.length*p);p!=w+x&&(e&&(!v||!w||x&&e/w>=v/x)?w++:x++);y=Math.min(w,1);u=w+1;b=0==w?Math.ceil(v/x):0==x?Math.ceil(e/w):Math.ceil(Math.max(e/w,v/x))}e='';if(m){e+='"}r&&(e=e+'");e+="";ca&&(c='");return e+"
'+A+'
';for(m=1;m<=p;m++){e+="";m==y&&f&&(e+='");m==u&&f&&(e+='");for(A=1;A<=b;A++)f&&m"}e+="
'+ -t[0]+"'+t[1]+"
'+this._generateHTMLMinutes(a),e+="
',E&&(c+='"),z&&(c+='"),J&&(c+='"),e+=c+"
"},_updateMinuteDisplay:function(a){var b=this._generateHTMLMinutes(a); -a.tpDiv.find("td.ui-timepicker-minutes").html(b);this._rebindDialogEvents(a)},_generateHTMLMinutes:function(a){var e,c="",f=this._get(a,"rows"),l=[];var n=this._get(a,"minutes");var m=null,r=0,t=1==this._get(a,"showMinutesLeadingZero"),p=this._get(a,"onMinuteShow"),w=this._get(a,"minuteText");n.starts||(n.starts=0);n.ends||(n.ends=59);n.manual||(n.manual=[]);for(e=n.starts;e<=n.ends;e+=n.interval)l.push(e);for(i=0;ie||59'+w+'';r=0;for(n=1;n<=f;n++){for(c+="";re&&t?"0"+e.toString():e.toString()),c+=this._generateHTMLMinuteCell(a, +var r=1==this._get(a,"showMinutes"),t=this._get(a,"amPmText"),p=this._get(a,"rows"),v=0,B=0,w=e=0,x=0,u=0,z=[],C=this._get(a,"hours"),D=0;var A=this._get(a,"hourText");var J=this._get(a,"showCloseButton"),F=this._get(a,"closeButtonText"),E=this._get(a,"showNowButton"),W=this._get(a,"nowButtonText"),y=this._get(a,"showDeselectButton"),Z=this._get(a,"deselectButtonText"),ca=J||E||y;for(b=C.starts;b<=C.ends;b++)z.push(b);b=Math.ceil(z.length/p);if(f){for(D=0;Dz[D]?e++:w++;D=0;v=Math.floor(e/ +z.length*p);B=Math.floor(w/z.length*p);p!=v+B&&(e&&(!w||!v||B&&e/v>=w/B)?v++:B++);x=Math.min(v,1);u=v+1;b=0==v?Math.ceil(w/B):0==B?Math.ceil(e/v):Math.ceil(Math.max(e/v,w/B))}e='
';if(m){e+='"}r&&(e=e+'");e+="";ca&&(c='");return e+"
'+A+'
';for(m=1;m<=p;m++){e+="";m==x&&f&&(e+='");m==u&&f&&(e+='");for(A=1;A<=b;A++)f&&m"}e+="
'+ +t[0]+"'+t[1]+"
'+this._generateHTMLMinutes(a),e+="
',E&&(c+='"),y&&(c+='"),J&&(c+='"),e+=c+"
"},_updateMinuteDisplay:function(a){var b=this._generateHTMLMinutes(a); +a.tpDiv.find("td.ui-timepicker-minutes").html(b);this._rebindDialogEvents(a)},_generateHTMLMinutes:function(a){var e,c="",f=this._get(a,"rows"),l=[];var n=this._get(a,"minutes");var m=null,r=0,t=1==this._get(a,"showMinutesLeadingZero"),p=this._get(a,"onMinuteShow"),v=this._get(a,"minuteText");n.starts||(n.starts=0);n.ends||(n.ends=59);n.manual||(n.manual=[]);for(e=n.starts;e<=n.ends;e+=n.interval)l.push(e);for(i=0;ie||59'+v+'';r=0;for(n=1;n<=f;n++){for(c+="";re&&t?"0"+e.toString():e.toString()),c+=this._generateHTMLMinuteCell(a, e,p),r++;c+=""}return c+"
"},_generateHTMLHourCell:function(a,b,c,f){var e=b;12e&&f&&(e="0"+e);c=!0;f=this._get(a,"onHourShow");var g=this._get(a,"maxTime"),h=this._get(a,"minTime");if(void 0==b)return' ';f&&(c=f.apply(a.input?a.input[0]:null,[b]));c&&(!isNaN(parseInt(g.hour))&&b>g.hour&&(c=!1),!isNaN(parseInt(h.hour))&&b'+e.toString()+"":''+e.toString()+""},_generateHTMLMinuteCell:function(a,b,c){var e=!0,f=a.hours,g=this._get(a,"onMinuteShow"),k=this._get(a,"maxTime"),r=this._get(a,"minTime");g&&(e=g.apply(a.input?a.input[0]:null,[a.hours,b]));if(void 0==b)return' '; e&&null!==f&&(!isNaN(parseInt(k.hour))&&!isNaN(parseInt(k.minute))&&f>=k.hour&&b>k.minute&&(e=!1),!isNaN(parseInt(r.hour))&&!isNaN(parseInt(r.minute))&&f<=r.hour&&b'+c+"":''+c+""},_destroyTimepicker:function(a){var e= @@ -539,8 +539,8 @@ c),10),e.minutes=parseInt(b.substr(c+1),10)):!g||m&&!r?!g&&m&&(e.minutes=parseIn deselectTime:function(a){a=b(a.target).attr("data-timepicker-instance-id");a=b(a);a=this._getInst(a[0]);a.hours=-1;a.minutes=-1;this._updateSelectedValue(a);this._hideTimepicker()},selectHours:function(a){var e=b(a.currentTarget),c=e.attr("data-timepicker-instance-id"),f=parseInt(e.attr("data-hour"));a=a.data.fromDoubleClick;var l=b(c);c=this._getInst(l[0]);var n=1==this._get(c,"showMinutes");if(b.fgtimepicker._isDisabledTimepicker(l.attr("id")))return!1;e.parents(".ui-timepicker-hours:first").find("a").removeClass("ui-state-active"); e.children("a").addClass("ui-state-active");c.hours=f;e=this._get(c,"onMinuteShow");f=this._get(c,"maxTime");l=this._get(c,"minTime");(e||f.minute||l.minute)&&this._updateMinuteDisplay(c);this._updateSelectedValue(c);c._hoursClicked=!0;(c._minutesClicked||a||0==n)&&b.fgtimepicker._hideTimepicker();return!1},selectMinutes:function(a){var e=b(a.currentTarget),c=e.attr("data-timepicker-instance-id"),f=parseInt(e.attr("data-minute"));a=a.data.fromDoubleClick;c=b(c);var l=this._getInst(c[0]),n=1==this._get(l, "showHours");if(b.fgtimepicker._isDisabledTimepicker(c.attr("id")))return!1;e.parents(".ui-timepicker-minutes:first").find("a").removeClass("ui-state-active");e.children("a").addClass("ui-state-active");l.minutes=f;this._updateSelectedValue(l);l._minutesClicked=!0;(l._hoursClicked||a||0==n)&&b.fgtimepicker._hideTimepicker();return!1},_updateSelectedValue:function(a){var b=this._getParsedTime(a);a.input&&(a.input.val(b),a.input.trigger("change"));var e=this._get(a,"onSelect");e&&e.apply(a.input?a.input[0]: -null,[b,a]);this._updateAlternate(a,b);return b},_getParsedTime:function(a){if(-1==a.hours&&-1==a.minutes)return"";if(a.hoursa.hours.ends)a.hours=0;if(a.minutesa.minutes.ends)a.minutes=0;var b="",e=1==this._get(a,"showPeriod"),c=1==this._get(a,"showLeadingZero"),f=1==this._get(a,"showHours"),n=1==this._get(a,"showMinutes"),m=1==this._get(a,"optionalMinutes"),r=this._get(a,"amPmText"),t=a.hours?a.hours:0,p=a.minutes?a.minutes:0,w=t?t:0;t="";-1== -w&&(w=0);-1==p&&(p=0);e&&(0==a.hours&&(w=12),12>a.hours?b=r[0]:(b=r[1],12w&&(e="0"+e);c=p.toString();10>p&&(c="0"+c);f&&(t+=e);!f||!n||m&&0==c||(t+=this._get(a,"timeSeparator"));!n||m&&0==c||(t+=c);f&&0a.hours.ends)a.hours=0;if(a.minutesa.minutes.ends)a.minutes=0;var b="",e=1==this._get(a,"showPeriod"),c=1==this._get(a,"showLeadingZero"),f=1==this._get(a,"showHours"),n=1==this._get(a,"showMinutes"),m=1==this._get(a,"optionalMinutes"),r=this._get(a,"amPmText"),t=a.hours?a.hours:0,p=a.minutes?a.minutes:0,v=t?t:0;t="";-1== +v&&(v=0);-1==p&&(p=0);e&&(0==a.hours&&(v=12),12>a.hours?b=r[0]:(b=r[1],12v&&(e="0"+e);c=p.toString();10>p&&(c="0"+c);f&&(t+=e);!f||!n||m&&0==c||(t+=this._get(a,"timeSeparator"));!n||m&&0==c||(t+=c);f&&0a.hours.ends)a.hours=0;if(a.minutesa.minutes.ends)a.minutes=0;return new Date(0,0,0,a.hours,a.minutes,0)},_getTimeTimepicker:function(a){a=this._getInst(a);return this._getParsedTime(a)},_getHourTimepicker:function(a){a=this._getInst(a);return void 0==a?-1:a.hours},_getMinuteTimepicker:function(a){a=this._getInst(a);return void 0==a?-1:a.minutes}});b.fn.fgtimepicker=function(a){b.fgtimepicker.initialized||(b(document).mousedown(b.fgtimepicker._checkExternalClick), b.fgtimepicker.initialized=!0);0===b("#"+b.fgtimepicker._mainDivId).length&&b("body").append(b.fgtimepicker.tpDiv);var c=Array.prototype.slice.call(arguments,1);return"string"==typeof a&&("getTime"==a||"getTimeAsDate"==a||"getHour"==a||"getMinute"==a)||"option"==a&&2==arguments.length&&"string"==typeof arguments[1]?b.fgtimepicker["_"+a+"Timepicker"].apply(b.fgtimepicker,[this[0]].concat(c)):this.each(function(){"string"==typeof a?b.fgtimepicker["_"+a+"Timepicker"].apply(b.fgtimepicker,[this].concat(c)): b.fgtimepicker._attachTimepicker(this,a)})};b.fgtimepicker=new a;b.fgtimepicker.initialized=!1;b.fgtimepicker.uuid=(new Date).getTime();b.fgtimepicker.version="0.3.3";window["TP_jQuery_"+f]=b})(jQuery); @@ -557,10 +557,10 @@ g._defaults.microsec
";B=0;for(C=this.units.length;B
";z=0;for(C=this.units.length;z
"));!0===c.timeOnly&&(D.prepend('
'+ -c.timeOnlyTitle+"
"),a.find(".ui-datepicker-header, .ui-datepicker-calendar").hide());B=0;for(C=e.units.length;B'+h+"";else for(g=c[f+"Min"];g<=k[f];g+=parseInt(c[f+"Grid"],10))l[f]++,u+=''+(10>g?"0":"")+g+"";u+=""}u+=""}z=null!==c.showTimezone?c.showTimezone:this.support.timezone;u+='";var D=b(u+('
"));!0===c.timeOnly&&(D.prepend('
'+ +c.timeOnlyTitle+"
"),a.find(".ui-datepicker-header, .ui-datepicker-calendar").hide());z=0;for(C=e.units.length;za?a+=12:-1!==g.indexOf("a")&&12===a&&(a=0));e.control.value(e,e[c+"_slider"],f,a);e._onTimeChange();e._onSelectHandler()}).css({cursor:"pointer",width:100/l[f]+"%",textAlign:"center",overflow:"hidden"}));this.timezone_select=D.find(".ui_tpicker_timezone").append("").find("select"); b.fn.append.apply(this.timezone_select,b.map(c.timezoneList,function(a,c){return b("");b.optional&&(e=0,c.unshift(''))}return{options:c.join(""),defaultValue:e}},g=function(b,g,k,m,r){var h=!1;if(1'+(b.msgNoRecords||"-")+"");else{var f=b.fncPrep,g=null;if(f)try{g=f(a)}catch(u){}f=b.lookupField||"id";for(var h=b.fncRepresent,k,l,w,x=0;x'+w+"");b.optional&&(e=0,c.unshift(''))}return{options:c.join(""),defaultValue:e}},g=function(b,g,k,m,r){var h=!1;if(1").addClass(a.attr("class")).attr("id",a.attr("id")).attr("name",a.attr("name")).data("visible",a.data("visible")).hide(),a.replaceWith(f),a=f);f=""===b;a.html(b).val(e).change().prop("disabled",f);a.hasClass("groupedopts-widget")?a.groupedopts("refresh"):a.hasClass("multiselect-widget")&&(a.multiselect("refresh"),f?a.multiselect("disable"):a.multiselect("enable"))}a.data("visible")&&(a.hasClass("groupedopts-widget")?l||a.groupedopts("show"):a.show());c(a,n)});f(n,g,k);r||S3ClearNavigateAwayConfirm()}}); -h.data("update-request",x)}},k=function(a){var b="";if("checkbox"==a.attr("type")){var c=a.closest(".checkboxes-widget-s3");c&&(a=c)}if(a.hasClass("checkboxes-widget-s3"))b=[],a.find("input:checked").each(function(){b.push($(this).val())});else if(a.hasClass("s3-hierarchy-input")){if(b="",c=a.val())c=JSON.parse(c),c.constructor===Array?c.length&&(b=c[0]):c&&(b=c)}else 1==a.length&&(b=a.val());return[a,b]};$.filterOptionsS3=function(a){var c=a.trigger;c=a.event?a.event:"string"==typeof c?c:c.name; -var e=a.lookupKey||c,f=b(a.trigger),h=b(a.target);if(h){var t=$(h);if(t.length){var p=t.closest("form");if(f&&(t=p.find(f),t.length)){$(f).each(function(){var b=$(this);var c=b.closest(".inline-form");if(!c.length||!c.hasClass("empty-row")&&"none"!=c.css("display"))c="row"==a.scope?b.closest(".edit-row.inline-form,.add-row.inline-form"):p,b=k(b),c=c.find(h),g(c,e,b[1],a,!1)});var w="triggerUpdate."+c;p.undelegate(f,"change.s3options").delegate(f,"change.s3options",function(){var a=k($(this));p.trigger(w, -a)});p.on(w,function(b,c,f){b=("row"==a.scope?c.closest(".edit-row.inline-form,.add-row.inline-form"):p).find(h);g(b,e,f,a,!0)})}}}}})(jQuery);(function(){$.cancelButtonS3=function(b){var a=$(".s3-cancel");if(a.length){var c=document.referrer;if(c&&c.split("?")[0].split("#")[0]!=document.URL.split("?")[0].split("#")[0]){var f=document.createElement("a");f.href=c;f.host==window.location.host&&0===f.pathname.lastIndexOf(S3.Ap,0)?a.attr("href",c):b?a.attr("href",b):a.hide()}else b?a.attr("href",b):a.hide()}}})(jQuery); +h.data("update-request",B)}},k=function(a){var b="";if("checkbox"==a.attr("type")){var c=a.closest(".checkboxes-widget-s3");c&&(a=c)}if(a.hasClass("checkboxes-widget-s3"))b=[],a.find("input:checked").each(function(){b.push($(this).val())});else if(a.hasClass("s3-hierarchy-input")){if(b="",c=a.val())c=JSON.parse(c),c.constructor===Array?c.length&&(b=c[0]):c&&(b=c)}else 1==a.length&&(b=a.val());return[a,b]};$.filterOptionsS3=function(a){var c=a.trigger;c=a.event?a.event:"string"==typeof c?c:c.name; +var e=a.lookupKey||c,f=b(a.trigger),h=b(a.target);if(h){var t=$(h);if(t.length){var p=t.closest("form");if(f&&(t=p.find(f),t.length)){$(f).each(function(){var b=$(this);var c=b.closest(".inline-form");if(!c.length||!c.hasClass("empty-row")&&"none"!=c.css("display"))c="row"==a.scope?b.closest(".edit-row.inline-form,.add-row.inline-form"):p,b=k(b),c=c.find(h),g(c,e,b[1],a,!1)});var v="triggerUpdate."+c;p.undelegate(f,"change.s3options").delegate(f,"change.s3options",function(){var a=k($(this));p.trigger(v, +a)});p.on(v,function(b,c,f){b=("row"==a.scope?c.closest(".edit-row.inline-form,.add-row.inline-form"):p).find(h);g(b,e,f,a,!0)})}}}}})(jQuery);(function(){$.cancelButtonS3=function(b){var a=$(".s3-cancel");if(a.length){var c=document.referrer;if(c&&c.split("?")[0].split("#")[0]!=document.URL.split("?")[0].split("#")[0]){var f=document.createElement("a");f.href=c;f.host==window.location.host&&0===f.pathname.lastIndexOf(S3.Ap,0)?a.attr("href",c):b?a.attr("href",b):a.hide()}else b?a.attr("href",b):a.hide()}}})(jQuery); S3.slider=function(b,a,c,f,e){var g=$("#"+b),k="#"+b+"_slider";$(k).slider({min:a,max:c,step:f,value:e,slide:function(a,b){g.val(b.value)},change:function(){if(null==e){e=(a+c)/2;var b=e%f;0!=b&&(e=b"+i18n.slider_help+"

"));g.closest("form").submit(function(){g.prop("disabled",!1);return!0})}; S3.range_slider=function(b,a,c,f,e){var g=$("#"+b+"_1"),k=$("#"+b+"_2"),h="#"+b+"_slider";$(h).slider({range:!0,min:a,max:c,step:f,values:e,slide:function(a,b){g.val(b.values[0]);k.val(b.values[1])},change:function(e,g){e=g.value;g=g.handleIndex;var k=$("#"+b+"_"+(g+1));if(null==e){e=(a+c)/2;var l=e%f;0!=l&&(e=l"+i18n.slider_help+ "

"));g.closest("form").submit(function(){g.prop("disabled",!1);k.prop("disabled",!1);return!0})}; @@ -705,26 +705,120 @@ $("#socialmedia_share").append(""));$(".form-toggle").click(function(){var a=$(this),b=$(this).data("hidden");b=b&&"False"!=b;a.data("hidden",b?!1:!0).siblings().slideToggle("medium",function(){a.children("span").each(function(){$(this).text(b?$(this).data("off"):$(this).data("on")).siblings().toggle()})})});$("#menu-options-toggle,#list-filter-toggle").on("click",function(a){a.stopPropagation();var b=$(this);a=b.data("status");var c="menu-options-toggle"==this.id?$("#menu-options"):$("#list-filter");"off"==a?c.hide().removeClass("hide-for-small").slideDown(400, function(){b.data("status","on").text(b.data("on"))}):c.slideUp(400,function(){c.addClass("hide-for-small").show();b.data("status","off").text(b.data("off"))})});$(".s3-download-button").on("click",function(a){a.preventDefault();a.stopPropagation();if(a=$(this).data("url")){var b=document.getElementById("s3-download");null==b&&(b=document.createElement("iframe"),b.id="s3-download",b.style.visibility="hidden",document.body.appendChild(b));$("#s3-download").off("load").on("load",function(){var a=$(this); try{var b=JSON.parse(this.contentDocument.body.textContent).message}catch(l){a.dialog({title:"Download failed",width:500,height:300,close:function(){a.attr("src","").remove()}}).css({visibility:"visible",width:"100%"});return}alert(b)});b.src=a;return!1}})})})(); +$.fn.collapsible=function(b){b=$.extend({defaulthide:!0,symbolhide:"-",symbolshow:"+",imagehide:null,imageshow:null,xoffset:"-15",yoffset:"0"},b);var a=$.meta?$.extend({},b,$$.data()):b;b=$(this);a.imageshow&&(a.symbolshow='');a.imagehide&&(a.symbolhide='');var c=a.symbolshow;$("li",b).each(function(b){0<$(">ul, >ol",this).size()&&(a.defaulthide?$(">ul, >ol",this).hide():c=a.symbolhide,$(this).prepend(''+c+"").css("position","relative"))});$(".jcollapsible",b).click(function(){var b=$(this).parent();$(">ul, >ol",b).slideToggle("fast");$(this).html($(this).html()==a.symbolshow?a.symbolhide:a.symbolshow);return!1})}; (function(b){b.cluetip={version:"1.2.5",template:'

',setup:{insertionType:"appendTo",insertionElement:"body"},defaults:{multiple:!1,width:275,height:"auto",cluezIndex:97,positionBy:"auto",topOffset:15,leftOffset:15,local:!1,localPrefix:null, localIdSuffix:null,hideLocal:!0,attribute:"rel",titleAttribute:"title",splitTitle:"",escapeTitle:!1,showTitle:!0,cluetipClass:"default",hoverClass:"",waitImage:!0,cursor:"help",arrows:!1,dropShadow:!0,dropShadowSteps:6,sticky:!1,mouseOutClose:!1,activation:"hover",clickThrough:!0,tracking:!1,delayedClose:0,closePosition:"top",closeText:"Close",truncate:0,fx:{open:"show",openSpeed:""},hoverIntent:{sensitivity:3,interval:50,timeout:0},onActivate:function(a){return!0},onShow:function(a,b){},onHide:function(a, b){},ajaxCache:!0,ajaxProcess:function(a){return a=a.replace(/<(script|style|title)[^<]+<\/(script|style|title)>/gm,"").replace(/<(link|meta)[^>]+>/g,"")},ajaxSettings:{dataType:"html"},debug:!1}};var a,c={},f=0,e=0;b.fn.attrProp=b.fn.prop||b.fn.attr;b.fn.cluetip=function(g,k){function h(a,c){var e=a||"";c=c||"";"object"==typeof c?b.each(c,function(a,b){e+="-"+a+"-"+b}):"string"==typeof c&&(e+=c);return e}function l(a,c,e){e="";c=c.dropShadow&&c.dropShadowSteps?+c.dropShadowSteps:0;if(b.support.boxShadow)return c&& -(e="1px 1px "+c+"px rgba(0,0,0,0.5)"),a.css(b.support.boxShadow,e),!1;e=a.find(".cluetip-drop-shadow");if(c==e.length)return e;e.remove();e=[];for(var f=0;f';return e=b(e.join("")).css({position:"absolute",backgroundColor:"#000",zIndex:y-1,opacity:.1}).addClass("cluetip-drop-shadow").prependTo(a)}var n,m;"object"==typeof g&&(k=g,g=null);if("destroy"==g){var r=this.data("cluetip");r&&(b(r.selector).remove(),b.removeData(this,"title"),b.removeData(this, -"cluetip"));b(document).unbind(".cluetip");return this.unbind(".cluetip")}k=b.extend(!0,{},b.cluetip.defaults,k||{});f++;r=b.cluetip.backCompat||!k.multiple?"cluetip":"cluetip-"+f;var t="#"+r,p=b.cluetip.backCompat?"#":".",w=b.cluetip.setup.insertionType,x=b.cluetip.setup.insertionElement||"body";w=/appendTo|prependTo|insertBefore|insertAfter/.test(w)?w:"appendTo";var v=b(t);if(!v.length){v=b(b.cluetip.template)[w](x).attr("id",r).css({position:"absolute",display:"none"});var y=+k.cluezIndex;var u= -v.find(p+"cluetip-outer").css({position:"relative",zIndex:y});var B=v.find(p+"cluetip-inner");var C=v.find(p+"cluetip-title")}a=b("#cluetip-waitimage");a.length||(a=b("
").attr("id","cluetip-waitimage").css({position:"absolute"}));a.insertBefore(v).hide();var D=(parseInt(v.css("paddingLeft"),10)||0)+(parseInt(v.css("paddingRight"),10)||0);this.each(function(f){function r(){return!1}function w(a,b){var c=a.status;b.beforeSend(a.xhr,b);if("error"==c)b[c](a.xhr,a.textStatus);else if("success"== -c)b[c](a.data,a.textStatus,a.xhr);b.complete(a.xhr,b.textStatus)}var x=this,A=b(this),z=b.extend(!0,{},k,b.metadata?A.metadata():b.meta?A.data():A.data("cluetip")||{}),Z=!1,ca=!1,ia=0,P=z[z.attribute]||("href"==z.attribute?A.attr(z.attribute):A.attrProp(z.attribute)||A.attr(z.attribute)),da=z.cluetipClass;y=+z.cluezIndex;A.data("cluetip",{title:x.title,zIndex:y,selector:t});if(!P&&!z.splitTitle&&!g)return!0;z.local&&z.localPrefix&&(P=z.localPrefix+P);z.local&&z.hideLocal&&P&&b(P+":first").hide(); -var X=parseInt(z.topOffset,10),aa=parseInt(z.leftOffset,10),N,Q,G=isNaN(parseInt(z.height,10))?"auto":/\D/g.test(z.height)?z.height:z.height+"px",M,I,H,R,U,Y=parseInt(z.width,10)||275,S=Y+D+z.dropShadowSteps,T=this.offsetWidth,O,K,L,ba,V="title"!=z.attribute?A.attrProp(z.titleAttribute)||"":"";if(z.splitTitle){var ea=V.split(z.splitTitle);V=z.showTitle||""===ea[0]?ea.shift():""}z.escapeTitle&&(V=V.replace(/&/g,"&").replace(/>/g,">").replace(/O&&O>S||O+T+S+aa>ba?O-S-aa:T+O+aa;if("area"==x.tagName.toLowerCase()||"mouse"==z.positionBy||T+S>ba)L+20+S>ba?(v.addClass("cluetip-"+da),K=0<=L-S-aa?L-S-aa-parseInt(v.css("marginLeft"),10)+parseInt(B.css("marginRight"),10):L-S/2):K=L+aa;var m=0>K?l.pageY+X:l.pageY;if(0>K||"bottomTop"==z.positionBy)K=L+S/2>ba?ba/2-S/2:Math.max(L-S/2,0)}n.css({zIndex:A.data("cluetip").zIndex+1});v.css({left:K,zIndex:A.data("cluetip").zIndex});Q=b(window).height();if(g)"function"==typeof g&& -(g=g.call(x)),B.html(g),fa(m);else if(ea){l=ea.length;B.html(l?ea[0]:"");if(1'+ea[r]+"");fa(m)}else if(z.local||0===P.indexOf("#"))z.local&&(l=b(P+(/^#\S+$/.test(P)?"":":eq("+f+")")).clone(!0).show(),z.localIdSuffix&&l.attr("id",l[0].id+z.localIdSuffix),B.html(l),fa(m));else if(/\.(jpe?g|tiff?|gif|png)(?:\?.*)?$/i.test(P))B.html(''+V+''),fa(m);else{var D=z.ajaxSettings.beforeSend,J=z.ajaxSettings.error,F=z.ajaxSettings.success, -E=z.ajaxSettings.complete;var y=h(P,z.ajaxSettings.data);l=b.extend(!0,{},z.ajaxSettings,{cache:z.ajaxCache,url:P,beforeSend:function(b,c){D&&D.call(x,b,v,B,c);u.children().empty();z.waitImage&&a.css({top:R+20,left:L+20,zIndex:A.data("cluetip").zIndex-1}).show()},error:function(a,b){k.ajaxCache&&!c[y]&&(c[y]={status:"error",textStatus:b,xhr:a});ca&&(J?J.call(x,a,b,v,B):B.html("sorry, the contents could not be loaded"))},success:function(a,b,e){k.ajaxCache&&!c[y]&&(c[y]={status:"success",data:a, -textStatus:b,xhr:e});Z=z.ajaxProcess.call(x,a);"object"==typeof Z&&null!==Z&&(V=Z.title,Z=Z.content);ca&&(F&&F.call(x,a,b,v,B),B.html(Z))},complete:function(c,f){E&&E.call(x,c,f,v,B);var g=B[0].getElementsByTagName("img");e=g.length;c=0;for(f=g.length;c'+z.closeText+""),"bottom"==z.closePosition?c.appendTo(B):"title"==z.closePosition?c.prependTo(C):c.prependTo(B),c.bind("click.cluetip",function(){ha();return!1}),z.mouseOutClose?v.bind("mouseleave.cluetip",function(){ha()}):v.unbind("mouseleave.cluetip"));u.css({zIndex:A.data("cluetip").zIndex,overflow:"auto"== -G?"visible":"auto",height:G});N="auto"==G?Math.max(v.outerHeight(),v.height()):parseInt(G,10);H=I;U=M+Q;"fixed"==z.positionBy?H=I-z.dropShadowSteps+X:KL||"bottomTop"==z.positionBy?I+N+X>U&&R-M>N+X?(H=R-N-X,f="top"):(H=R+X,f="bottom"):H=I+N+X>U?N>=Q?M:U-N-X:"block"==A.css("display")||"area"==x.tagName.toLowerCase()||"mouse"==z.positionBy?a-X:I-z.dropShadowSteps;""===f&&(f=Ke?e:f)+"px"),n.css({top:e}).show()):n.hide();(m=l(v,z))&&m.length&&m.hide().css({height:N,width:Y,zIndex:A.data("cluetip").zIndex-1}).show();v.hide()[z.fx.open](z.fx.openSpeed||0);b.fn.bgiframe&&v.bgiframe();0';return e=b(e.join("")).css({position:"absolute",backgroundColor:"#000",zIndex:x-1,opacity:.1}).addClass("cluetip-drop-shadow").prependTo(a)}var n,m;"object"==typeof g&&(k=g,g=null);if("destroy"==g){var r=this.data("cluetip");r&&(b(r.selector).remove(),b.removeData(this,"title"),b.removeData(this, +"cluetip"));b(document).unbind(".cluetip");return this.unbind(".cluetip")}k=b.extend(!0,{},b.cluetip.defaults,k||{});f++;r=b.cluetip.backCompat||!k.multiple?"cluetip":"cluetip-"+f;var t="#"+r,p=b.cluetip.backCompat?"#":".",v=b.cluetip.setup.insertionType,B=b.cluetip.setup.insertionElement||"body";v=/appendTo|prependTo|insertBefore|insertAfter/.test(v)?v:"appendTo";var w=b(t);if(!w.length){w=b(b.cluetip.template)[v](B).attr("id",r).css({position:"absolute",display:"none"});var x=+k.cluezIndex;var u= +w.find(p+"cluetip-outer").css({position:"relative",zIndex:x});var z=w.find(p+"cluetip-inner");var C=w.find(p+"cluetip-title")}a=b("#cluetip-waitimage");a.length||(a=b("
").attr("id","cluetip-waitimage").css({position:"absolute"}));a.insertBefore(w).hide();var D=(parseInt(w.css("paddingLeft"),10)||0)+(parseInt(w.css("paddingRight"),10)||0);this.each(function(f){function r(){return!1}function v(a,b){var c=a.status;b.beforeSend(a.xhr,b);if("error"==c)b[c](a.xhr,a.textStatus);else if("success"== +c)b[c](a.data,a.textStatus,a.xhr);b.complete(a.xhr,b.textStatus)}var B=this,A=b(this),y=b.extend(!0,{},k,b.metadata?A.metadata():b.meta?A.data():A.data("cluetip")||{}),Z=!1,ca=!1,ia=0,P=y[y.attribute]||("href"==y.attribute?A.attr(y.attribute):A.attrProp(y.attribute)||A.attr(y.attribute)),da=y.cluetipClass;x=+y.cluezIndex;A.data("cluetip",{title:B.title,zIndex:x,selector:t});if(!P&&!y.splitTitle&&!g)return!0;y.local&&y.localPrefix&&(P=y.localPrefix+P);y.local&&y.hideLocal&&P&&b(P+":first").hide(); +var X=parseInt(y.topOffset,10),aa=parseInt(y.leftOffset,10),N,Q,G=isNaN(parseInt(y.height,10))?"auto":/\D/g.test(y.height)?y.height:y.height+"px",M,I,H,R,U,Y=parseInt(y.width,10)||275,S=Y+D+y.dropShadowSteps,T=this.offsetWidth,O,K,L,ba,V="title"!=y.attribute?A.attrProp(y.titleAttribute)||"":"";if(y.splitTitle){var ea=V.split(y.splitTitle);V=y.showTitle||""===ea[0]?ea.shift():""}y.escapeTitle&&(V=V.replace(/&/g,"&").replace(/>/g,">").replace(/O&&O>S||O+T+S+aa>ba?O-S-aa:T+O+aa;if("area"==B.tagName.toLowerCase()||"mouse"==y.positionBy||T+S>ba)L+20+S>ba?(w.addClass("cluetip-"+da),K=0<=L-S-aa?L-S-aa-parseInt(w.css("marginLeft"),10)+parseInt(z.css("marginRight"),10):L-S/2):K=L+aa;var m=0>K?l.pageY+X:l.pageY;if(0>K||"bottomTop"==y.positionBy)K=L+S/2>ba?ba/2-S/2:Math.max(L-S/2,0)}n.css({zIndex:A.data("cluetip").zIndex+1});w.css({left:K,zIndex:A.data("cluetip").zIndex});Q=b(window).height();if(g)"function"==typeof g&& +(g=g.call(B)),z.html(g),fa(m);else if(ea){l=ea.length;z.html(l?ea[0]:"");if(1'+ea[r]+"");fa(m)}else if(y.local||0===P.indexOf("#"))y.local&&(l=b(P+(/^#\S+$/.test(P)?"":":eq("+f+")")).clone(!0).show(),y.localIdSuffix&&l.attr("id",l[0].id+y.localIdSuffix),z.html(l),fa(m));else if(/\.(jpe?g|tiff?|gif|png)(?:\?.*)?$/i.test(P))z.html(''+V+''),fa(m);else{var D=y.ajaxSettings.beforeSend,J=y.ajaxSettings.error,F=y.ajaxSettings.success, +E=y.ajaxSettings.complete;var x=h(P,y.ajaxSettings.data);l=b.extend(!0,{},y.ajaxSettings,{cache:y.ajaxCache,url:P,beforeSend:function(b,c){D&&D.call(B,b,w,z,c);u.children().empty();y.waitImage&&a.css({top:R+20,left:L+20,zIndex:A.data("cluetip").zIndex-1}).show()},error:function(a,b){k.ajaxCache&&!c[x]&&(c[x]={status:"error",textStatus:b,xhr:a});ca&&(J?J.call(B,a,b,w,z):z.html("sorry, the contents could not be loaded"))},success:function(a,b,e){k.ajaxCache&&!c[x]&&(c[x]={status:"success",data:a, +textStatus:b,xhr:e});Z=y.ajaxProcess.call(B,a);"object"==typeof Z&&null!==Z&&(V=Z.title,Z=Z.content);ca&&(F&&F.call(B,a,b,w,z),z.html(Z))},complete:function(c,f){E&&E.call(B,c,f,w,z);var g=z[0].getElementsByTagName("img");e=g.length;c=0;for(f=g.length;c'+y.closeText+""),"bottom"==y.closePosition?c.appendTo(z):"title"==y.closePosition?c.prependTo(C):c.prependTo(z),c.bind("click.cluetip",function(){ha();return!1}),y.mouseOutClose?w.bind("mouseleave.cluetip",function(){ha()}):w.unbind("mouseleave.cluetip"));u.css({zIndex:A.data("cluetip").zIndex,overflow:"auto"== +G?"visible":"auto",height:G});N="auto"==G?Math.max(w.outerHeight(),w.height()):parseInt(G,10);H=I;U=M+Q;"fixed"==y.positionBy?H=I-y.dropShadowSteps+X:KL||"bottomTop"==y.positionBy?I+N+X>U&&R-M>N+X?(H=R-N-X,f="top"):(H=R+X,f="bottom"):H=I+N+X>U?N>=Q?M:U-N-X:"block"==A.css("display")||"area"==B.tagName.toLowerCase()||"mouse"==y.positionBy?a-X:I-y.dropShadowSteps;""===f&&(f=Ke?e:f)+"px"),n.css({top:e}).show()):n.hide();(m=l(w,y))&&m.length&&m.hide().css({height:N,width:Y,zIndex:A.data("cluetip").zIndex-1}).show();w.hide()[y.fx.open](y.fx.openSpeed||0);b.fn.bgiframe&&w.bgiframe();0");f.cssclass&&("inherit"==f.cssclass?p.attr("class",b(a).attr("class")):p.attr("class",f.cssclass));f.style&&("inherit"==f.style?(p.attr("style",b(a).attr("style")),p.css("display",b(a).css("display"))):p.attr("style",f.style));var C=l.apply(p,[f,a]);if(f.loadurl){var D=setTimeout(function(){C.disabled=!0;h.apply(p,[f.loadtext,f,a])},100);u={};u[f.id]=a.id;b.isFunction(f.loaddata)?b.extend(u,f.loaddata.apply(a, +[a.revert,f])):b.extend(u,f.loaddata);b.ajax({type:f.loadtype,url:f.loadurl,data:u,async:!1,success:function(a){window.clearTimeout(D);A=a;C.disabled=!1}})}else if(f.data){var A=f.data;b.isFunction(f.data)&&(A=f.data.apply(a,[a.revert,f]))}else A=a.revert;h.apply(p,[A,f,a]);C.attr("name",f.name);k.apply(p,[f,a]);b(a).append(p);e.apply(p,[f,a]);b(":input:visible:enabled:first",p).focus();f.select&&C.select();C.keydown(function(b){27==b.keyCode&&(b.preventDefault(),n.apply(p,[f,a]))});"cancel"==f.onblur? +C.blur(function(b){D=setTimeout(function(){n.apply(p,[f,a])},500)}):"submit"==f.onblur?C.blur(function(a){D=setTimeout(function(){p.submit()},200)}):b.isFunction(f.onblur)?C.blur(function(b){f.onblur.apply(a,[C.val(),f])}):C.blur(function(a){});p.submit(function(c){D&&clearTimeout(D);c.preventDefault();if(!1!==t.apply(p,[f,a])&&!1!==g.apply(p,[f,a]))if(b.isFunction(f.target))c=f.target.apply(a,[C.val(),f]),b(a).html(c),a.editing=!1,m.apply(a,[a.innerHTML,f]),b.trim(b(a).html())||b(a).html(f.placeholder); +else{c={};c[f.name]=C.val();c[f.id]=a.id;b.isFunction(f.submitdata)?b.extend(c,f.submitdata.apply(a,[a.revert,f])):b.extend(c,f.submitdata);"PUT"==f.method&&(c._method="put");b(a).html(f.indicator);var e={type:"POST",data:c,dataType:"html",url:f.target,success:function(c,g){"html"==e.dataType&&b(a).html(c);a.editing=!1;m.apply(a,[c,f]);b.trim(b(a).html())||b(a).html(f.placeholder)},error:function(b,c,e){v.apply(p,[f,a,b])}};b.extend(e,f.ajaxoptions);b.ajax(e)}b(a).attr("title",f.tooltip);return!1})}}); +this.reset=function(c){this.editing&&!1!==p.apply(c,[f,a])&&(b(a).html(a.revert),a.editing=!1,b.trim(b(a).html())||b(a).html(f.placeholder),f.tooltip&&b(a).attr("title",f.tooltip))}})}};b.editable={types:{defaults:{element:function(a,c){a=b('');b(this).append(a);return a},content:function(a,c,f){b(":input:first",this).val(a)},reset:function(a,b){b.reset(this)},buttons:function(a,c){var f=this;if(a.submit){if(a.submit.match(/>$/))var e=b(a.submit).click(function(){"submit"!= +e.attr("type")&&f.submit()});else e=b('