From 156fe4dc0ce552faa93c1f8294a53da8cccf4cba Mon Sep 17 00:00:00 2001 From: Daniel Beasley Date: Mon, 26 Jun 2023 14:34:03 +0100 Subject: [PATCH] initial commit --- .gitignore | 10 + README.md | 32 + builder/__init__.py | 0 builder/handle_csv.py | 118 + builder/handle_file_plugin.py | 111 + builder/handle_file_turbine.py | 122 + builder/handle_vm.py | 321 + builder/html_vm_edit.py | 495 + builder/html_vm_report.py | 288 + builder/loader_redcap.py | 565 + builder/loader_xlsx.py | 486 + builder/schema.py | 335 + builder_templates/174/ProjectSelector.vm | 96 + builder_templates/174/SubjectFinder.vm | 72 + builder_templates/174/action_box_template.vm | 15 + builder_templates/174/image_edit_form17.vm | 153 + builder_templates/174/image_report_form17.vm | 116 + builder_templates/174/macros/xMacros.vm | 469 + .../plugin_template/entities/Template.java | 30 + .../plugin/XnatTemplatePlugin.java | 49 + .../preferences/TemplatePreferencesBean.java | 54 + .../repositories/TemplateRepository.java | 18 + .../174/plugin_template/rest/TemplateApi.java | 102 + .../rest/TemplatePrefsApi.java | 96 + .../services/TemplateService.java | 24 + .../impl/HibernateTemplateService.java | 32 + builder_templates/174/scripts/formmaker.js | 14 + .../174/scripts/jquery-timepicker/AUTHORS | 17 + .../174/scripts/jquery-timepicker/CHANGELOG | 88 + .../174/scripts/jquery-timepicker/LICENSE-GPL | 287 + .../174/scripts/jquery-timepicker/LICENSE-MIT | 36 + .../174/scripts/jquery-timepicker/README.md | 23 + .../jquery-timepicker/jquery.timepicker.css | 84 + .../jquery-timepicker/jquery.timepicker.js | 864 + .../jquery.timepicker.min.css | 11 + .../jquery.timepicker.min.js | 10 + .../images/ui-bg_flat_0_aaaaaa_40x100.png | Bin 0 -> 251 bytes .../images/ui-bg_flat_75_ffffff_40x100.png | Bin 0 -> 247 bytes .../images/ui-bg_glass_55_fbf9ee_1x400.png | Bin 0 -> 374 bytes .../images/ui-bg_glass_65_ffffff_1x400.png | Bin 0 -> 246 bytes .../images/ui-bg_glass_75_dadada_1x400.png | Bin 0 -> 301 bytes .../images/ui-bg_glass_75_e6e6e6_1x400.png | Bin 0 -> 301 bytes .../images/ui-bg_glass_95_fef1ec_1x400.png | Bin 0 -> 371 bytes .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin 0 -> 319 bytes .../images/ui-icons_222222_256x240.png | Bin 0 -> 7006 bytes .../images/ui-icons_2e83ff_256x240.png | Bin 0 -> 4599 bytes .../images/ui-icons_454545_256x240.png | Bin 0 -> 7071 bytes .../images/ui-icons_888888_256x240.png | Bin 0 -> 7092 bytes .../images/ui-icons_cd0a0a_256x240.png | Bin 0 -> 4599 bytes .../images/ui-icons_ffffff_256x240.png | Bin 0 -> 7196 bytes .../174/scripts/jquery-ui/jquery-ui-xnat.css | 1227 ++ .../174/scripts/jquery-ui/jquery-ui.css | 1225 ++ .../174/scripts/jquery-ui/jquery-ui.js | 16582 ++++++++++++++++ .../174/scripts/jquery-ui/jquery-ui.min.js | 13 + .../174/scripts/jquery/jquery-1.8.3.js | 9472 +++++++++ .../174/scripts/jquery/jquery-1.8.3.min.js | 2 + .../174/scripts/jquery/jquery.cookie.js | 61 + .../174/scripts/jquery/jquery.js | 9472 +++++++++ .../174/scripts/jquery/jquery.min.js | 2 + builder_templates/174/subject_edit_form17.vm | 129 + .../174/subject_report_form17.vm | 56 + .../174/turbine_java_template.java | 84 + .../174/turbine_java_template_session.java | 116 + .../174/xnat_edit_subjectAssessorData.vm | 15 + builder_templates/176/ProjectSelector.vm | 96 + builder_templates/176/SubjectFinder.vm | 72 + builder_templates/176/action_box_template.vm | 20 + builder_templates/176/formmaker.js | 48 + builder_templates/176/image_edit_form17.vm | 172 + builder_templates/176/image_report_form17.vm | 116 + builder_templates/176/macros/xMacros.vm | 469 + .../plugin_template/entities/Template.java | 30 + .../plugin/XnatTemplatePlugin.java | 51 + .../preferences/TemplatePreferencesBean.java | 54 + .../repositories/TemplateRepository.java | 18 + .../176/plugin_template/rest/TemplateApi.java | 111 + .../rest/TemplatePrefsApi.java | 98 + .../services/TemplateService.java | 24 + .../impl/HibernateTemplateService.java | 32 + .../176/scripts/jquery-timepicker/AUTHORS | 17 + .../176/scripts/jquery-timepicker/CHANGELOG | 88 + .../176/scripts/jquery-timepicker/LICENSE-GPL | 287 + .../176/scripts/jquery-timepicker/LICENSE-MIT | 36 + .../176/scripts/jquery-timepicker/README.md | 23 + .../jquery-timepicker/jquery.timepicker.css | 84 + .../jquery-timepicker/jquery.timepicker.js | 864 + .../jquery.timepicker.min.css | 11 + .../jquery.timepicker.min.js | 10 + .../images/ui-bg_flat_0_aaaaaa_40x100.png | Bin 0 -> 251 bytes .../images/ui-bg_flat_75_ffffff_40x100.png | Bin 0 -> 247 bytes .../images/ui-bg_glass_55_fbf9ee_1x400.png | Bin 0 -> 374 bytes .../images/ui-bg_glass_65_ffffff_1x400.png | Bin 0 -> 246 bytes .../images/ui-bg_glass_75_dadada_1x400.png | Bin 0 -> 301 bytes .../images/ui-bg_glass_75_e6e6e6_1x400.png | Bin 0 -> 301 bytes .../images/ui-bg_glass_95_fef1ec_1x400.png | Bin 0 -> 371 bytes .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin 0 -> 319 bytes .../images/ui-icons_222222_256x240.png | Bin 0 -> 7006 bytes .../images/ui-icons_2e83ff_256x240.png | Bin 0 -> 4599 bytes .../images/ui-icons_454545_256x240.png | Bin 0 -> 7071 bytes .../images/ui-icons_888888_256x240.png | Bin 0 -> 7092 bytes .../images/ui-icons_cd0a0a_256x240.png | Bin 0 -> 4599 bytes .../images/ui-icons_ffffff_256x240.png | Bin 0 -> 7196 bytes .../176/scripts/jquery-ui/jquery-ui-xnat.css | 1227 ++ .../176/scripts/jquery-ui/jquery-ui.css | 1225 ++ .../176/scripts/jquery-ui/jquery-ui.js | 16582 ++++++++++++++++ .../176/scripts/jquery-ui/jquery-ui.min.js | 13 + .../176/scripts/jquery/jquery-1.8.3.js | 9472 +++++++++ .../176/scripts/jquery/jquery-1.8.3.min.js | 2 + .../176/scripts/jquery/jquery.cookie.js | 61 + .../176/scripts/jquery/jquery.js | 9472 +++++++++ .../176/scripts/jquery/jquery.min.js | 2 + builder_templates/176/subject_edit_form17.vm | 141 + .../176/subject_report_form17.vm | 62 + .../176/turbine_java_template.java | 91 + .../176/turbine_java_template_session.java | 116 + .../176/xnat_edit_subjectAssessorData.vm | 15 + data/.keep | 0 examples/RT.xlsx | Bin 0 -> 30309 bytes examples/dasher-anonymiser.xlsx | Bin 0 -> 26214 bytes examples/dd2.csv | 434 + examples/demo.xlsx | Bin 0 -> 26600 bytes examples/kclqc.xlsx | Bin 0 -> 25413 bytes examples/msk-TEST.xlsx | Bin 0 -> 47636 bytes examples/rttqa-plugin-ot-windows.xlsx | Bin 0 -> 34085 bytes examples/rttqa-qaforms.xlsx | Bin 0 -> 34932 bytes examples/wikidemo.xlsx | Bin 0 -> 24993 bytes examples/xnat_form_RT_HN_May2020.xlsx | Bin 0 -> 43005 bytes .../.gradle/4.9/fileChanges/last-build.bin | Bin 0 -> 1 bytes .../.gradle/4.9/fileContent/fileContent.lock | Bin 0 -> 17 bytes .../174/.gradle/4.9/fileHashes/fileHashes.bin | Bin 0 -> 18897 bytes .../.gradle/4.9/fileHashes/fileHashes.lock | Bin 0 -> 17 bytes .../174/.gradle/vcsWorkingDirs/gc.properties | 0 gradle_templates/174/build.gradle | 213 + .../174/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 52928 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + gradle_templates/174/gradlew | 169 + gradle_templates/174/gradlew.bat | 84 + gradle_templates/174/settings.gradle | 10 + .../.gradle/4.9/fileChanges/last-build.bin | Bin 0 -> 1 bytes .../176/.gradle/4.9/fileHashes/fileHashes.bin | Bin 0 -> 18747 bytes .../.gradle/4.9/fileHashes/fileHashes.lock | Bin 0 -> 17 bytes .../176/.gradle/vcsWorkingDirs/gc.properties | 0 gradle_templates/176/build.gradle | 168 + .../176/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54413 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + gradle_templates/176/gradlew | 172 + gradle_templates/176/gradlew.bat | 84 + gradle_templates/176/settings.gradle | 10 + makeforms.py | 292 + plugin-spreadsheet-template.xlsx | Bin 0 -> 25483 bytes plugins_restrict_project.sh | 13 + redcap_dict/.keep | 0 xlsx/.keep | 0 xnat.cfg | 9 + 154 files changed, 87081 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 builder/__init__.py create mode 100644 builder/handle_csv.py create mode 100644 builder/handle_file_plugin.py create mode 100644 builder/handle_file_turbine.py create mode 100644 builder/handle_vm.py create mode 100644 builder/html_vm_edit.py create mode 100644 builder/html_vm_report.py create mode 100644 builder/loader_redcap.py create mode 100644 builder/loader_xlsx.py create mode 100644 builder/schema.py create mode 100644 builder_templates/174/ProjectSelector.vm create mode 100644 builder_templates/174/SubjectFinder.vm create mode 100644 builder_templates/174/action_box_template.vm create mode 100644 builder_templates/174/image_edit_form17.vm create mode 100644 builder_templates/174/image_report_form17.vm create mode 100644 builder_templates/174/macros/xMacros.vm create mode 100644 builder_templates/174/plugin_template/entities/Template.java create mode 100644 builder_templates/174/plugin_template/plugin/XnatTemplatePlugin.java create mode 100644 builder_templates/174/plugin_template/preferences/TemplatePreferencesBean.java create mode 100644 builder_templates/174/plugin_template/repositories/TemplateRepository.java create mode 100644 builder_templates/174/plugin_template/rest/TemplateApi.java create mode 100644 builder_templates/174/plugin_template/rest/TemplatePrefsApi.java create mode 100644 builder_templates/174/plugin_template/services/TemplateService.java create mode 100644 builder_templates/174/plugin_template/services/impl/HibernateTemplateService.java create mode 100644 builder_templates/174/scripts/formmaker.js create mode 100644 builder_templates/174/scripts/jquery-timepicker/AUTHORS create mode 100644 builder_templates/174/scripts/jquery-timepicker/CHANGELOG create mode 100644 builder_templates/174/scripts/jquery-timepicker/LICENSE-GPL create mode 100644 builder_templates/174/scripts/jquery-timepicker/LICENSE-MIT create mode 100644 builder_templates/174/scripts/jquery-timepicker/README.md create mode 100644 builder_templates/174/scripts/jquery-timepicker/jquery.timepicker.css create mode 100644 builder_templates/174/scripts/jquery-timepicker/jquery.timepicker.js create mode 100644 builder_templates/174/scripts/jquery-timepicker/jquery.timepicker.min.css create mode 100644 builder_templates/174/scripts/jquery-timepicker/jquery.timepicker.min.js create mode 100644 builder_templates/174/scripts/jquery-ui/images/ui-bg_flat_0_aaaaaa_40x100.png create mode 100644 builder_templates/174/scripts/jquery-ui/images/ui-bg_flat_75_ffffff_40x100.png create mode 100644 builder_templates/174/scripts/jquery-ui/images/ui-bg_glass_55_fbf9ee_1x400.png create mode 100644 builder_templates/174/scripts/jquery-ui/images/ui-bg_glass_65_ffffff_1x400.png create mode 100644 builder_templates/174/scripts/jquery-ui/images/ui-bg_glass_75_dadada_1x400.png create mode 100644 builder_templates/174/scripts/jquery-ui/images/ui-bg_glass_75_e6e6e6_1x400.png create mode 100644 builder_templates/174/scripts/jquery-ui/images/ui-bg_glass_95_fef1ec_1x400.png create mode 100644 builder_templates/174/scripts/jquery-ui/images/ui-bg_highlight-soft_75_cccccc_1x100.png create mode 100644 builder_templates/174/scripts/jquery-ui/images/ui-icons_222222_256x240.png create mode 100644 builder_templates/174/scripts/jquery-ui/images/ui-icons_2e83ff_256x240.png create mode 100644 builder_templates/174/scripts/jquery-ui/images/ui-icons_454545_256x240.png create mode 100644 builder_templates/174/scripts/jquery-ui/images/ui-icons_888888_256x240.png create mode 100644 builder_templates/174/scripts/jquery-ui/images/ui-icons_cd0a0a_256x240.png create mode 100644 builder_templates/174/scripts/jquery-ui/images/ui-icons_ffffff_256x240.png create mode 100644 builder_templates/174/scripts/jquery-ui/jquery-ui-xnat.css create mode 100644 builder_templates/174/scripts/jquery-ui/jquery-ui.css create mode 100644 builder_templates/174/scripts/jquery-ui/jquery-ui.js create mode 100644 builder_templates/174/scripts/jquery-ui/jquery-ui.min.js create mode 100644 builder_templates/174/scripts/jquery/jquery-1.8.3.js create mode 100644 builder_templates/174/scripts/jquery/jquery-1.8.3.min.js create mode 100644 builder_templates/174/scripts/jquery/jquery.cookie.js create mode 100644 builder_templates/174/scripts/jquery/jquery.js create mode 100644 builder_templates/174/scripts/jquery/jquery.min.js create mode 100644 builder_templates/174/subject_edit_form17.vm create mode 100644 builder_templates/174/subject_report_form17.vm create mode 100644 builder_templates/174/turbine_java_template.java create mode 100644 builder_templates/174/turbine_java_template_session.java create mode 100644 builder_templates/174/xnat_edit_subjectAssessorData.vm create mode 100644 builder_templates/176/ProjectSelector.vm create mode 100644 builder_templates/176/SubjectFinder.vm create mode 100644 builder_templates/176/action_box_template.vm create mode 100644 builder_templates/176/formmaker.js create mode 100644 builder_templates/176/image_edit_form17.vm create mode 100644 builder_templates/176/image_report_form17.vm create mode 100644 builder_templates/176/macros/xMacros.vm create mode 100644 builder_templates/176/plugin_template/entities/Template.java create mode 100644 builder_templates/176/plugin_template/plugin/XnatTemplatePlugin.java create mode 100644 builder_templates/176/plugin_template/preferences/TemplatePreferencesBean.java create mode 100644 builder_templates/176/plugin_template/repositories/TemplateRepository.java create mode 100644 builder_templates/176/plugin_template/rest/TemplateApi.java create mode 100644 builder_templates/176/plugin_template/rest/TemplatePrefsApi.java create mode 100644 builder_templates/176/plugin_template/services/TemplateService.java create mode 100644 builder_templates/176/plugin_template/services/impl/HibernateTemplateService.java create mode 100644 builder_templates/176/scripts/jquery-timepicker/AUTHORS create mode 100644 builder_templates/176/scripts/jquery-timepicker/CHANGELOG create mode 100644 builder_templates/176/scripts/jquery-timepicker/LICENSE-GPL create mode 100644 builder_templates/176/scripts/jquery-timepicker/LICENSE-MIT create mode 100644 builder_templates/176/scripts/jquery-timepicker/README.md create mode 100644 builder_templates/176/scripts/jquery-timepicker/jquery.timepicker.css create mode 100644 builder_templates/176/scripts/jquery-timepicker/jquery.timepicker.js create mode 100644 builder_templates/176/scripts/jquery-timepicker/jquery.timepicker.min.css create mode 100644 builder_templates/176/scripts/jquery-timepicker/jquery.timepicker.min.js create mode 100644 builder_templates/176/scripts/jquery-ui/images/ui-bg_flat_0_aaaaaa_40x100.png create mode 100644 builder_templates/176/scripts/jquery-ui/images/ui-bg_flat_75_ffffff_40x100.png create mode 100644 builder_templates/176/scripts/jquery-ui/images/ui-bg_glass_55_fbf9ee_1x400.png create mode 100644 builder_templates/176/scripts/jquery-ui/images/ui-bg_glass_65_ffffff_1x400.png create mode 100644 builder_templates/176/scripts/jquery-ui/images/ui-bg_glass_75_dadada_1x400.png create mode 100644 builder_templates/176/scripts/jquery-ui/images/ui-bg_glass_75_e6e6e6_1x400.png create mode 100644 builder_templates/176/scripts/jquery-ui/images/ui-bg_glass_95_fef1ec_1x400.png create mode 100644 builder_templates/176/scripts/jquery-ui/images/ui-bg_highlight-soft_75_cccccc_1x100.png create mode 100644 builder_templates/176/scripts/jquery-ui/images/ui-icons_222222_256x240.png create mode 100644 builder_templates/176/scripts/jquery-ui/images/ui-icons_2e83ff_256x240.png create mode 100644 builder_templates/176/scripts/jquery-ui/images/ui-icons_454545_256x240.png create mode 100644 builder_templates/176/scripts/jquery-ui/images/ui-icons_888888_256x240.png create mode 100644 builder_templates/176/scripts/jquery-ui/images/ui-icons_cd0a0a_256x240.png create mode 100644 builder_templates/176/scripts/jquery-ui/images/ui-icons_ffffff_256x240.png create mode 100644 builder_templates/176/scripts/jquery-ui/jquery-ui-xnat.css create mode 100644 builder_templates/176/scripts/jquery-ui/jquery-ui.css create mode 100644 builder_templates/176/scripts/jquery-ui/jquery-ui.js create mode 100644 builder_templates/176/scripts/jquery-ui/jquery-ui.min.js create mode 100644 builder_templates/176/scripts/jquery/jquery-1.8.3.js create mode 100644 builder_templates/176/scripts/jquery/jquery-1.8.3.min.js create mode 100644 builder_templates/176/scripts/jquery/jquery.cookie.js create mode 100644 builder_templates/176/scripts/jquery/jquery.js create mode 100644 builder_templates/176/scripts/jquery/jquery.min.js create mode 100644 builder_templates/176/subject_edit_form17.vm create mode 100644 builder_templates/176/subject_report_form17.vm create mode 100644 builder_templates/176/turbine_java_template.java create mode 100644 builder_templates/176/turbine_java_template_session.java create mode 100644 builder_templates/176/xnat_edit_subjectAssessorData.vm create mode 100644 data/.keep create mode 100644 examples/RT.xlsx create mode 100644 examples/dasher-anonymiser.xlsx create mode 100644 examples/dd2.csv create mode 100644 examples/demo.xlsx create mode 100644 examples/kclqc.xlsx create mode 100644 examples/msk-TEST.xlsx create mode 100755 examples/rttqa-plugin-ot-windows.xlsx create mode 100644 examples/rttqa-qaforms.xlsx create mode 100755 examples/wikidemo.xlsx create mode 100644 examples/xnat_form_RT_HN_May2020.xlsx create mode 100644 gradle_templates/174/.gradle/4.9/fileChanges/last-build.bin create mode 100644 gradle_templates/174/.gradle/4.9/fileContent/fileContent.lock create mode 100644 gradle_templates/174/.gradle/4.9/fileHashes/fileHashes.bin create mode 100644 gradle_templates/174/.gradle/4.9/fileHashes/fileHashes.lock create mode 100644 gradle_templates/174/.gradle/vcsWorkingDirs/gc.properties create mode 100644 gradle_templates/174/build.gradle create mode 100644 gradle_templates/174/gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle_templates/174/gradle/wrapper/gradle-wrapper.properties create mode 100644 gradle_templates/174/gradlew create mode 100644 gradle_templates/174/gradlew.bat create mode 100644 gradle_templates/174/settings.gradle create mode 100644 gradle_templates/176/.gradle/4.9/fileChanges/last-build.bin create mode 100644 gradle_templates/176/.gradle/4.9/fileHashes/fileHashes.bin create mode 100644 gradle_templates/176/.gradle/4.9/fileHashes/fileHashes.lock create mode 100644 gradle_templates/176/.gradle/vcsWorkingDirs/gc.properties create mode 100755 gradle_templates/176/build.gradle create mode 100644 gradle_templates/176/gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle_templates/176/gradle/wrapper/gradle-wrapper.properties create mode 100755 gradle_templates/176/gradlew create mode 100755 gradle_templates/176/gradlew.bat create mode 100755 gradle_templates/176/settings.gradle create mode 100644 makeforms.py create mode 100644 plugin-spreadsheet-template.xlsx create mode 100755 plugins_restrict_project.sh create mode 100644 redcap_dict/.keep create mode 100644 xlsx/.keep create mode 100644 xnat.cfg diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6a2dee7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.idea/ +test.sh +test176.sh +/plugins/*jar +/plugins/ +/xlsx/*xlsx +/redcap_dict/*.csv +/builder/__pycache__ + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..b5bf6f8 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +Please read the wiki for instructions. + +## Updates +**26 June 2023**: +* Subforms added +* Gradlew bug fixed - restarts gradle to clear cache, as can break if schema changed. +* Date box and ynuo bugs fixed +* Session/subject assessor label format changed to prevent same day clashes + +**22 March 2023**: +* Redcap rewritten and improved - includeding calcsum and datediff +* Bug fix: float textboxes did not work - came out string. Fixed +* Project-specific field added +* Using modified XNAT 1.8 Plugin Template (not working) +* Tested on XNAT 1.8.7 + +**22 March 2023**: +* Redcap rewritten and improved - includeding calcsum and datediff +* Bug fix: float textboxes did not work - came out string. Fixed +* Project-specific field added +* Using modified XNAT 1.8 Plugin Template +* Tested on XNAT 1.8.7 + +**26 Jan 2023**: +* Subforms added (else potentially not buildable in gradle due to being too long) +* Tested to XNATv1.8.6.1 + +## Limitations + +* Redcap Calc fields are limited. +* UK Date formats dd-MM-YYYY + diff --git a/builder/__init__.py b/builder/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/builder/handle_csv.py b/builder/handle_csv.py new file mode 100644 index 0000000..34ae003 --- /dev/null +++ b/builder/handle_csv.py @@ -0,0 +1,118 @@ +import os +import shutil + + + + +class CSVHandler(object): + + def __init__(self, configData, deleteExistingFiles=False): + self.errors = [] + self.log = [] + self.configData = configData + self.schemaName = configData['schemaName'] + self.csvDir = '{}/data'.format(configData['path_plugin_builder']) + + + if (deleteExistingFiles): + self.emptyTheDir() + + def hasError(self): + return len(self.errors) + + def errors(self): + return self.errors + + + + + def emptyTheDir(self): + + thePath = ('{}/data/{}').format(self.configData['path_plugin_builder'], self.schemaName) + if os.path.exists(thePath): + self.log.append(' Delete Dir : {}'.format(thePath)) + shutil.rmtree(thePath) + #make new dir + + os.makedirs(thePath) + + + def getHeader(self, form): + + out = 'ID,' + for i in form['inputs']: + el = '{}:{}/{}'.format(self.schemaName, form['ref'], i['dataName']) + if 'subform' in i['inputFormType']: + + subform = i['options'] + #insert questions later + out = '{}{}@{}@,'.format(out, el,subform) + + elif len(i['children']) > 0: + elsub = '{}/{}'.format(el, i['subName']) + out = '{}{},'.format(out, elsub) + for ii in i['children']: + elsub = '{}/{}'.format(el, ii['subName']) + if 'subform' in ii['inputFormType']: + subform = ii['options'] + # insert questions later + out = '{}{}@{}@,'.format(out, elsub, subform) + else: + out = '{}{},'.format(out, el) + out = '{}subject_ID'.format(out ) + return out + + def getLineOfData(self, form, data): + + + form_label = '{}-{}'.format(form['ref'],data['subject_label']) + out = '{},'.format(form_label) + + for i in data['inputs']: + out = '{},{},'.format(out, data[i]) + out = '{},{}'.format(out, data['subject_label'] ) + return out + + def writeToFile(self, form): + + head = self.getHeader(form) + + if len(head) == 0: + self.errors.append('no inputs') + return + + writeFileName = '{}/{}/{}_{}.csv'.format(self.csvDir,self.schemaName,self.schemaName, form['ref']) + #self.checkDir(writeFileName) + self.log.append(' write file : {}'.format(writeFileName)) + f = open(writeFileName, "w") + + f.write('{}'.format(head)) + + f.close() + + def write_subforms(self, subform): + thePath = ('{}/data/{}').format(self.configData['path_plugin_builder'], self.schemaName) + subform_replace = '@{}@'.format(subform['ref']) + out = '' + if os.path.exists(thePath): + for filename in os.listdir(thePath): + filepath = os.path.join(thePath, filename) + with open(filepath) as f: + header = f.readline() + heads = header.split(',') + for head in heads: + print(head) + if subform_replace in head: + el = head.replace(subform_replace,'') + print(el) + for i in subform['inputs']: + out = '{},{}/{}'.format(out,el, i['dataName']) + + else: + out = '{},{}'.format(out,head) + + with open(filepath, "w") as f: + #get rid of first and last comma + out = out[1:-1] + f.write(out) + diff --git a/builder/handle_file_plugin.py b/builder/handle_file_plugin.py new file mode 100644 index 0000000..afb863a --- /dev/null +++ b/builder/handle_file_plugin.py @@ -0,0 +1,111 @@ +import os +import shutil + +class PluginHandler(object): + + def __init__(self, configData, deleteExistingFiles = False): + + self.errors = [] + self.log =[] + self.configData = configData + self.schemaName = configData['schemaName'] + + self.part_plugin_path = 'src/main/java/org/nrg/xnat/plugins/' + + def hasError(self): + return len(self.errors) + + def errors(self): + return self.errors + + + + + def copyTemplatesReplace(self,toDir): + + fromDir = ('{}/plugin_template'.format(self.configData['path_builder_templates'])) + + self.log.append(' copy plugin template from : {}'.format(fromDir)) + self.log.append(' copy plugin template to : {}'.format(toDir)) + if os.path.exists(toDir): + shutil.rmtree(toDir) + + shutil.copytree( fromDir,toDir , symlinks=False, ignore=None) + + for root, dirs, files in os.walk(toDir): + for file in files: + if file == '.DS_Store': + continue + f_in = os.path.join(root, file) + # print ('File : ', f_in) + fxsd = open(f_in,"r") + lines = fxsd.readlines() + fxsd.close() + fxsd = open(f_in,"w") + + for line in lines: + fxsd.write(line.replace('[HERE_SCHEMANAME_LOWER]',self.schemaName.lower()).replace('[HERE_SCHEMANAME]',self.schemaName.title())) + + fxsd.close() + + for root, dirs, files in os.walk(toDir): + for file in files: + fo = os.path.join(root, file) + if 'Template' in file: + print ('Renaming {} to {} '.format(file, file.replace('Template',self.schemaName.title()).replace('template',self.schemaName.lower()))) + os.rename(fo,os.path.join(root, file.replace('Template',self.schemaName.title()).replace('template',self.schemaName.lower()))) + if 'template' in file: + print ('Renaming {} to {} '.format(file, file.replace('Template',self.schemaName.title()).replace('template',self.schemaName.lower()))) + os.rename(fo,os.path.join(root, file.replace('template',self.schemaName.lower()))) + + + def createFiles(self,forms): + + toDir = ('{}/{}/{}{}').format(self.configData['path_builder_plugins'],self.schemaName, self.part_plugin_path,self.configData['schemaName'].lower()) + + self.copyTemplatesReplace(toDir) + + HERE_IMPORT_BEANS =[] + HERE_XNAT_DATA_MODELS =[] + + for f in forms: + sc = self.schemaName[:1].upper() + self.schemaName[1:].lower() + st = forms[f]['ref'][:1].upper() + forms[f]['ref'][1:].lower() + bean = '{}{}Bean'.format(sc,st) + formName = forms[f]['name'] + md = '@XnatDataModel(value = {}.SCHEMA_ELEMENT_NAME, singular = "{}", plural = "{}")'.format(bean,formName,formName) + + HERE_IMPORT_BEANS.append('import org.nrg.xdat.bean.{};'.format(bean) ) + HERE_XNAT_DATA_MODELS.append(md) + + + file_plugin = ('{}/plugin/Xnat{}Plugin.java'.format(toDir,self.schemaName.title())) + print('FILE', file_plugin) + f = open(file_plugin,"r") + lines = f.readlines() + f.close() + + # print(lines) + self.log.append(' replace file contents : {}'.format(file_plugin)) + + f = open(file_plugin,"w") + for line in lines: + # print(line) + if '[HERE_IMPORT_BEANS]' in line: + f.write(line.replace('[HERE_IMPORT_BEANS]','\n'.join(HERE_IMPORT_BEANS))) + elif '[HERE_XNAT_DATA_MODELS]' in line: + f.write(line.replace('[HERE_XNAT_DATA_MODELS]',','.join(HERE_XNAT_DATA_MODELS))) + elif '[HERE_CLASS_NAME]' in line: + f.write(line.replace('[HERE_CLASS_NAME]',sc)) + elif '[HERE_SCHEMANAME_LOWER]' in line: + f.write(line.replace('[HERE_SCHEMANAME_LOWER]',self.configData['schemaName'].lower())) + elif '[HERE_PLUGIN_NAME]' in line: + f.write(line.replace('[HERE_PLUGIN_NAME]',self.configData['schemaName'])) + else: + # print(line) + f.write(line) + + f.close() + + + diff --git a/builder/handle_file_turbine.py b/builder/handle_file_turbine.py new file mode 100644 index 0000000..d2f7562 --- /dev/null +++ b/builder/handle_file_turbine.py @@ -0,0 +1,122 @@ +import errno +import os +import shutil + +class TurbineHandler(object): + + def __init__(self, configData, deleteExistingFiles = False): + + self.errors = [] + self.log =[] + self.configData = configData + self.schemaName = configData['schemaName'] + + self.filesDir = 'src/main/java/org/apache/turbine/app/xnat/modules/screens/' + self.actionsDir = 'src/main/java/org/apache/turbine/app/xnat/modules/actions/' + + if(deleteExistingFiles): + self.emptyTheDir() + + def hasError(self): + return len(self.errors) + + + def checkDir(self,vmFile): + + if not os.path.exists(os.path.dirname(vmFile)): + try: + os.makedirs(os.path.dirname(vmFile)) + except OSError as exc: # Guard against race condition + if exc.errno != errno.EEXIST: + raise + + def emptyTheDir(self): + + thePath = ('{}/{}/{}').format(self.configData['path_builder_plugins'],self.schemaName,self.filesDir) + if os.path.exists(thePath): + shutil.rmtree(thePath) + + self.log.append(' Empty Dir : {}'.format(thePath)) + thePath = ('{}/{}/{}').format(self.configData['path_builder_plugins'],self.schemaName,self.actionsDir) + if os.path.exists(thePath): + shutil.rmtree(thePath) + + self.log.append(' Empty Dir : {}'.format(thePath)) + + def copyAndReplace(self,src,dst, form): + sc = self.schemaName[:1].upper() + self.schemaName[1:].lower() + st = form['ref'][:1].upper() + form['ref'][1:].lower() + om_name = '{}{}'.format(sc, st) + class_name = 'XDATScreen_edit_{}_{}'.format(self.schemaName, form['ref']) + label_tag = form['ref'] + + f = open(src, "r") + lines = f.readlines() + f.close() + + f = open(dst, "w") + for line in lines: + if '[SCHEMA_TITLE]' in line or '[OM_NAME]' in line or '[CLASS_NAME]' in line or '[LABEL_TAG]' in line or '[FORM_NAME]' in line: + line = line.replace('[OM_NAME]', om_name) + line = line.replace('[CLASS_NAME]', class_name) + line = line.replace('[FORM_NAME]', form['ref']) + line = line.replace('[SCHEMA_TITLE]',self.schemaName.title()) + f.write(line.replace('[LABEL_TAG]', str(label_tag))) + else: + f.write(line) + + f.close() + + def loadTemplateReplace(self, form): + + fname = ('{}/turbine_java_template.java'.format(self.configData['path_builder_templates'])) + if form['ext'] != 'subject': + #sesison level + fname = ('{}/turbine_java_template_session.java'.format(self.configData['path_builder_templates'])) + self.log.append(' load file : {}'.format(fname)) + + return fname + + + def copyTemplateScreeen(self, form): + src = ('{}/TemplateScreen.java').format(self.configData['path_builder_templates']) + dst = ('{}/{}/{}/{}Screen.java').format(self.configData['path_builder_plugins'], self.schemaName, self.filesDir,self.schemaName.title() ) + self.copyAndReplace(src,dst, form) + + def copyModifyTemplateScreeen(self,form): + #under actions dir + #for subjects only as dunno about session + if 'ubject' in form['ext']: + + dst = ('{}/{}/{}/').format(self.configData['path_builder_plugins'], self.schemaName,self.actionsDir) + if not os.path.exists(os.path.dirname(dst)): + try: + os.makedirs(os.path.dirname(dst)) + except OSError as exc: # Guard against race condition + if exc.errno != errno.EEXIST: + raise + self.log.append(' write file : {}'.format(dst)) + #DONT USE title() - as does not work if form contains numbers + ref = form['ref'][:1].upper() + form['ref'][1:].lower() + + dst = ('{}/{}/{}/Modify{}{}.java').format(self.configData['path_builder_plugins'], self.schemaName, self.actionsDir, self.schemaName.title(), ref) + src = ('{}/ModifyTemplateSample.java').format(self.configData['path_builder_templates']) + self.copyAndReplace(src,dst, form) + + + def pathTemplateReplaceOut(self,templateType,formRef): + + fname = ('{}/{}/{}XDATScreen_{}').format(self.configData['path_builder_plugins'],self.schemaName,self.filesDir,templateType) + return fname + ('_{}_{}.java').format(self.configData['schemaName'],formRef) + + + def formEdit(self,form): + + + templateName = self.loadTemplateReplace(form) + writeFileName = self.pathTemplateReplaceOut('edit',form['ref']) + + self.log.append(' write file : {}'.format(writeFileName)) + self.checkDir(writeFileName) + + self.copyAndReplace(templateName,writeFileName, form) diff --git a/builder/handle_vm.py b/builder/handle_vm.py new file mode 100644 index 0000000..1780d77 --- /dev/null +++ b/builder/handle_vm.py @@ -0,0 +1,321 @@ +import os +import sys +import shutil + + +import builder.html_vm_report +import builder.html_vm_edit + +class VMHandler(object): + + def __init__(self, configData, deleteExistingFiles = False): + + self.errors = [] + self.log =[] + self.configData = configData + self.schemaName = configData['schemaName'] + self.restrict_to_project= configData['restrict_to_project'] + + self.filesDir = 'src/main/resources/META-INF/resources/templates/screens/' + self.actionBoxDirSubject = 'xnat_subjectData/actionsBox' + self.actionBoxDirImage = 'xnat_imageSessionData/actionsBox' + + if(deleteExistingFiles): + self.emptyTheDir() + self.emptyTheDir() + + def hasError(self): + return len(self.errors) + + def errors(self): + return self.errors + + def checkDir(self,vmFile): + + if not os.path.exists(os.path.dirname(vmFile)): + try: + os.makedirs(os.path.dirname(vmFile)) + except OSError as exc: # Guard against race condition + if exc.errno != errno.EEXIST: + raise + + def emptyTheDir(self): + + thePath = ('{}/{}/{}').format(self.configData['path_builder_plugins'],self.schemaName, self.filesDir) + if os.path.exists(thePath): + self.log.append(' Delete Dir : {}'.format(thePath)) + shutil.rmtree(thePath) + + + def loadTemplateReplace(self,templateType,entryType): + + if templateType != 'subject': + templateType = 'image' + + fname = ('{}/{}_{}_form17.vm'.format(self.configData['path_builder_templates'],templateType,entryType)) + self.log.append(' load file : {}'.format(fname)) + + f = open(fname,"r") + lines = f.readlines() + f.close() + return lines + + def pathTemplateReplaceOut(self,templateType,formRef): + + pathPlugin = ('{}/{}'.format(self.configData['path_builder_plugins'],self.schemaName)) + fname = ('{}/{}XDATScreen_{}').format(pathPlugin,self.filesDir,templateType) + return fname + ('_{}_{}.vm').format(self.configData['schemaName'],formRef) + + def copyTemplateScreeen(self): + src = ('{}/TemplateScreen.vm').format(self.configData['path_builder_templates']) + dst = ('{}/{}/{}/{}Screen.vm').format(self.configData['path_builder_plugins'], self.schemaName, self.filesDir,self.schemaName.title()) + #shutil.copy(src,dst) + + f = open(src,"r") + lines = f.readlines() + f.close() + f = open(dst, "w") + for line in lines: + if '[SCHEMA_NAME]' in line: + line = line.replace('[SCHEMA_NAME]', self.schemaName.title()) + f.write(line) + f.close() + + + def copyJavascript(self): + # copy jquery and other scripts into plugin + js_dest = '{}/{}/src/main/resources/META-INF/resources/scripts/'.format(self.configData['path_builder_plugins'],self.schemaName) + scriptsDir= ('{}/{}').format(self.configData['path_builder_templates'],'scripts') + if not os.path.exists(js_dest): + shutil.copytree( scriptsDir, js_dest , symlinks=False, ignore=None) + #copy the cusotm javascript + js_file = ('{}/formmaker.js').format(self.configData['path_builder_templates']) + shutil.copyfile( js_file,'{}/formmaker{}.js'.format( js_dest,self.configData['schemaName'])) + + def copyMacros(self): + + macro_file = ('{}/macros/xMacros.vm').format(self.configData['path_builder_templates']) + macro_dest = '{}/{}/src/main/resources/META-INF/resources/templates/macros/'.format(self.configData['path_builder_plugins'],self.schemaName) + + if not os.path.exists(macro_dest): + os.makedirs(macro_dest) + shutil.copyfile( macro_file,'{}/{}Macros.vm'.format( macro_dest,self.configData['schemaName'])) + + def handleFormSubforms(self,form,screenType,inputs): + + file_type = '_report_' + if 'edit' in screenType: + file_type='_edit_' + + #find all references for subform in forms, and replace + if len(inputs) == 0 : + self.errors.append('no inputs') + return + formDir = ('{}/{}/{}').format(self.configData['path_builder_plugins'], self.schemaName, self.filesDir) + for fl in os.listdir(formDir): + print(fl) + if file_type in fl: + print(os.path.join(formDir, fl)) + f = open(os.path.join(formDir, fl), "r") + lines = f.readlines() + f.close() + f = open(os.path.join(formDir, fl), "w") + for line in lines: + if '@{}@'.format(form['ref']) in line: + print('REPLACING LINE - ', line) + #must be subform type - insert inputs + #'line format' @BASE_SCHEMA@ @SUBFORM NAME form['name']@ + new_schema_base=line.split("@")[1] + #f.write(line) + f.write(line.replace(line, '\n'.join(inputs).replace('@BASE_SCHEMA@', new_schema_base))) + else: + f.write(line) + f.close() + + + def handleFormByExtScreen(self,form,screenType,inputs, jscript=''): + + # print('inputs',inputs) + if len(inputs) == 0 : + self.errors.append('no inputs') + return + + replaceTemplate = self.loadTemplateReplace(form['ext'],screenType) + writeFileName = self.pathTemplateReplaceOut(screenType,form['ref']) + + self.checkDir(writeFileName) + self.log.append(' write file : {}'.format(writeFileName)) + + f = open(writeFileName,"w") + + for line in replaceTemplate: + if '[HERE_MODIFY_FORM_ACTION]' in line: + ref = form['ref'][:1].upper() + form['ref'][1:].lower() + line = line.replace('[HERE_MODIFY_FORM_ACTION]', '{}{}'.format(self.schemaName.title(),ref)) + if '[DATA_TYPE]' in line: + f.write(line.replace('[DATA_TYPE]','{}:{}'.format(self.schemaName,form['ref']))) + elif '[HERE_FORM_TITLE]' in line: + f.write(line.replace('[HERE_FORM_TITLE]', form['name'])) + elif '[HERE_FORM_REF]' in line: + f.write(line.replace('[HERE_FORM_REF]', form['ref'])) + elif '[HERE_FORM_INPUTS]' in line: + try: + f.write(line.replace('[HERE_FORM_INPUTS]', '\n'.join(inputs))) + except Exception as e: + print('Error writing to {} - error: {}'.format(writeFileName ,e)) + for n in inputs: + print('Error {}'.format(n)) + sys.exit(0) + + elif '[CUSTOM JAVASCRIPT]' in line: + f.write(line.replace('[CUSTOM JAVASCRIPT]', '\n'.join(jscript))) + elif 'xMacro' in line: + f.write(line.replace('xMacro', '{}Macro'.format(self.schemaName))) + elif 'formmakerx.js' in line: + f.write(line.replace('formmakerx.js', 'formmaker{}.js'.format(self.schemaName))) + else: + f.write(line) + + f.close() + + def logInput(self,msg,i): + + self.log.append(' {} : {} : sub name: {} : form type : {}'.format(msg,i['question'], i['subName'], i['inputFormType'])) + + def formEdit(self,form): + + inputs = [] + # self.errors.append('eee') + self.log.append(' loop Form Ext Screens Edit : {}'.format(form['name']) ) + inputs.extend(self.formInputsEdit(form)) + + jscript = [] + jscript.extend(self.formJSEdit(form)) + + + # print('inputs',inputs) + if len(inputs) == 0 : + self.errors.append('no inputs') + return + if 'subform' in form['ext']: + print('SUBFORM making form...') + self.handleFormSubforms(form,'edit', inputs) + else: + self.handleFormByExtScreen(form,'edit',inputs, jscript) + #cop marcos + self.copyMacros() + self.copyJavascript() + + + + def formInputsEdit(self,form): + #forms=['all forms here'] + + out =[] + subform = False + if 'subform' in form['ext']: + subform = True + + for i in form['inputs']: + out.extend(builder.html_vm_edit.htmlInputEdit(i, self.schemaName, form['ref'], subform)) + + return out + + def formJSEdit(self, form): + + out = [] + for i in form['inputs']: + if 'calc' in i['inputFormType']: + self.log.append('edit screen CALC : {}'.format(i['question'])) + out.extend(builder.html_vm_edit.editTypeCalcJScript(i,self.schemaName,form['ref'])) + return out + + def formReport(self,form): + + inputs = [] + + self.log.append(' loop Form Ext Screens Report : {}'.format(form['name']) ) + + inputs.extend(self.formInputsReport(form)) + + if len(inputs) == 0 : + self.errors.append('no inputs') + return + + if 'subform' in form['ext']: + print('SUBFORM making form...') + self.handleFormSubforms(form,'report', inputs) + else: + self.handleFormByExtScreen(form,'report',inputs) + + + def formInputsReport(self,form): + subform = False + if 'subform' in form['ext']: + subform = True + + out =[] + for i in form['inputs']: + + el = '{}:{}/{}'.format(self.schemaName,form['ref'],i['dataName']) + self.log.append('report screen el : {}'.format(el)) + + out.extend(builder.html_vm_report.htmlInputReport(i,self.schemaName,form['ref'], subform)) + + return out + + + + + + def add_to_actionsbox(self,form,sequence, version): + print( 'Making sub-actions box' ) + boxDir = self.actionBoxDirSubject if (form['ext'] == 'subject') else self.actionBoxDirImage + + fname = ('{}/{}/{}{}/{}_assessors.vm').format(self.configData['path_builder_plugins'],self.schemaName, self.filesDir,boxDir,self.schemaName) + self.checkDir(fname) + if not os.path.isfile(fname): + try: + fname = ('{}/action_box_template.vm').format(self.configData['path_builder_templates']) + print('assessors.vm does not exist, using template {}'.format(fname)) + except OSError as exc: # Guard against race condition + if exc.errno != errno.EEXIST: + raise + + f = open(fname,"r") + lines = f.readlines() + f.close() + + fname = ('{}/{}/{}{}/{}_assessors.vm').format(self.configData['path_builder_plugins'],self.schemaName, self.filesDir,boxDir,self.schemaName) + vm_file = ('XDATScreen_edit_{}_{}').format(self.schemaName,form['ref']) + f = open(fname,"w") + + for line in lines: + if '[PROJECT]' in line: + f.write(line.replace('[PROJECT]', self.restrict_to_project)) + elif '[SCHEMA]' in line: + f.write(line.replace('[SCHEMA]', self.schemaName)) + else: + f.write(line) + if 'ADD_HERE' in line: + + f.write('
  • '.format(sequence)) + if form['ext'] == 'subject': + if '18' in version: + f.write('') + else: + f.write( ' ') + else: + f.write('' ) + f.write('
     
    Add {}
  • \n'.format(form['name'])) + + + f.close() + print ('Created {}'.format(fname) ) + + + \ No newline at end of file diff --git a/builder/html_vm_edit.py b/builder/html_vm_edit.py new file mode 100644 index 0000000..0624dd3 --- /dev/null +++ b/builder/html_vm_edit.py @@ -0,0 +1,495 @@ +import time +## Bracnhing - this is redcap branching, not excell spreadhseets + + +def get_panel_name(input, tog_name): + panel_name = 'panel{}'.format(tog_name) + if 'panelNum' in input: + panel_name = 'panel{}{}'.format(tog_name, input['panelNum']) + return panel_name + +def htmlTextString(input,el, out): + iType = input['inputFormType'] + if 'subheader' in iType: + print('SUBHEADER - {}'.format(input['question'])) + out.extend(editTypeSubHeader(input)) + elif 'eader' in iType: + print('HEADER - {}'.format(input['question'])) + out.extend(editTypeHeader(input)) + elif 'insert' in iType: + print('PARAGRAPH - {}'.format(input['question'])) + out.extend(editTypeHtml(input)) + elif 'paragraph' in iType: + print('PARAGRAPH - {}'.format(input['question'])) + out.extend(editTypeParagraph(input)) + + +def htmlInputEditStandard(input,el, out): + + iType = input['inputFormType'] + #print(iType) + + if 'branchingquestion' in input: + out.extend(editTypeBranch(input, el, 'start')) + + if 'text' not in input['schemaInputType']: + if iType != 'multiradio' and 'xstart' not in el: + out.append('') + out.append('{}'.format(input['question'])) + out.append(' ') + + if 'branchVals' in input: + #assumes radio.... + out.extend(editBranchToggle(input, el)) + + elif iType == 'subform': + out.extend(editTypeSubform(input, el)) + + elif iType == 'multiradio': + + out.extend(editTypeMultiRadio(input, el)) + + elif iType == 'radio': + out.extend(editTypeRadio(input, el)) + + elif iType == 'dropdown': + out.extend(editTypeDropdown(input,el)) + + elif iType == 'textarea': + out.append(editTypeTextArea(el)) + + elif iType == 'tickbox': + out.append(editTypeTickBox(input, el)) + elif iType == 'date': + out.append(editTypeDate(el)) + elif iType == 'datetime': + out.append(editTypeDateTime(el)) + elif iType == 'time': + out.append(editTypeTime(el)) + elif 'calc' in iType: + print('CALC TYPE: {}'.format(input)) + out.append(editTypeCalc(input, el)) + elif 'text' in input['schemaInputType']: + htmlTextString(input, el, out) + else: + out.append(editTypeText(input,el)) + + out.append(' ') + out.append('') + + if 'branchingquestion' in input: + out.extend(editTypeBranch(input, el, 'end')) + + +def htmlInputEdit(input,schema,formRef, subform): + out = [] + + #print(input) + # Special types - shouldn't be here..... + toggle_panels = ['ynuo','yn','ny','ynu', 'multipanel'] + + + el = '{}:{}/{}'.format(schema, formRef, input['dataName']) + print('TEST EL, ',el, ' TEST EDIT TYPE,',input['inputFormType']) + if subform: + #must be subform, as includes main element: + el = '@BASE_SCHEMA@/{}'.format(input['dataName']) + + iType = input['inputFormType'] + + if 'x' in input['hide']: + print('Skipping {} - hidden datatype'.format(input['dataName'], input['inputFormType'])) + elif iType in toggle_panels: + print('Toggle panel....') + out.extend(editTogglePanel(input,el)) + elif len(input['children']) > 0: + print('{} - {} - has children...'.format(input['dataName'], input['inputFormType'])) + # check if toggle type with subs.... + + elsub ='{}/{}'.format(el,input['subName']) + htmlInputEditStandard(input, elsub, out) + for i in input['children']: + elsub = '{}/{}'.format(el, i['subName']) + print('TEST SUB EL, ', elsub, ' TEST EDIT TYPE,', i['inputFormType']) + if 'subheader' in i['inputFormType']: + out.extend(editTypeSubHeader(i)) + elif 'header' in i['inputFormType']: + out.extend(editTypeHeader(i)) + elif 'aragraph' in i['inputFormType']: + out.extend(editTypeParagraph(i)) + elif i['inputFormType'] in toggle_panels: + out.extend(editTogglePanel(i,elsub)) + else: + htmlInputEditStandard(i, elsub, out) + out.append('
    ') + else: + htmlInputEditStandard(input,el, out) + #print('{} - {}'.format(input['dataName'], input['inputFormType'])) + + return out + + +def editTypeCalc(input, el): + #schematype or inputtype? + #return '#fmCalcType("{}" $item $vr "{}")'.format(el, input['inputDataType']) + #rounddown, sum, datediff, + # datediff NOT from questions - they are entries in the same column! And has formatting.... + #datediff - [date1,date2, dateformatting options] + + out=[] + #datediff = dd + if 'dd' in input['inputFormType']: + + #too complex + #out.append(''.format(el,el)) + out = editTypeText(input,el) + elif 'sum' in input['inputFormType']: + print('SUM, making hidden') + out = ''.format(el,el) + else: + #if statment? just do text box for now + out = editTypeText(input,el) + + return out + + +def editTypeCalcJScript(input,schema,formRef): + # this creates the javascript at the end + # um datediff, can have rounddown and multiple complex datediffs, pluses and minuses + + el = '{}:{}/{}'.format(schema, formRef, input['dataName']) + print('test JS: type:{} '.format(input['inputFormType'])) + + out=[] + dtes = input['options'].split(' ') #already split.... no commas, just spaces.... + + if 'dd' in input['inputFormType']: + print('test JS - datediff: sc:{} fm:{} in:{}'.format(schema, formRef, input)) + print(input['rawopts']) + print('number of datediffs: {}'.format(input['rawopts'].count('datedif'))) + if input['rawopts'].count('datedif') == 1: + + if input['rawopts'].count(']') == 2: + #simple datediff - sometime ],[, sometimes ], [ + aarr = input['rawopts'].replace('_','').replace(' ','').split('],[') + dt1 = (aarr[0].split('['))[1] + dt2 = (aarr[1].split(']'))[0] + print('Found dates: {} {} '.format(dt1, dt2)) + dt1 = '{}:{}/{}'.format(schema, formRef, dt1) + dt2 = '{}:{}/{}'.format(schema, formRef, dt2) + + #time.sleep(2) + # day, minutes, month etc + format = 'd' + if '"m"' in aarr[1]: + format = 'm' + + out.append(' calcDateDiff(\"{}\",\"{}\", \"{}\", \"{}\" )'.format(dt1,dt2,el, format)) + + + elif 'sum' in input['inputFormType']: + print('test JS - sum: sc:{} fm:{} in:{} dtes:{}'.format(schema, formRef, input, dtes)) + first = True + for dt in dtes: + a = dt.replace('datediff', '').replace('sum', '').replace(')', '') + a = '{}:{}/{}'.format(schema, formRef, a.replace('_','')) + if first: + args = "[ \"{}\"".format(a) + first = False + else: + args = "{},\"{}\"".format(args,a ) + args = '{} ]'.format(args) + #sys.exit(0) + #args = args.replace(' ',',{}:{}/'.format(schema, formRef)) + + out.append(" calcSum(\"{}\", {})".format(el,args)) + return out + + + +def editTypeHeader(input): + head=[] + + head.append('') + head.append('{}'.format(input['question'])) + head.append(' ') + head.append(' ') + head.append('') + + return head + +def editTypeSubHeader(input): + head=[] + + head.append('') + head.append('{}'.format(input['question'])) + head.append(' ') + head.append(' ') + head.append('') + + return head + + + +def editTypeHtml(input): + head=[] + head.append(input['question']) + return head + +def editTypeParagraph(input): + head=[] + head.append('') + head.append('{}'.format(input['question'])) + head.append('') + return head + +def editTypeTickBox(input, el): + default = 'n' + if 'y' in input['options']: + default='y' + return '#fmBooleanCheckbox("{}" $item $vr "{}")'.format(el, default) + +def editTypeTextArea(el): + + return '#xdatTextArea("{}" $item "" $vr 3 60)'.format(el) + +def editTypeText(input,el): + if "default" in input: + return '#xdatTextBox("{}" $item "{}" $vr)'.format(el, input['default']) + return '#xdatTextBox("{}" $item "" $vr)'.format(el) + +def editTypeSubform(input, el): + #format @BASE_ELEMENT@ @SUBFORM@ - repalced later on + out = [] + out.append( '@{}@ @{}@'.format(el, input['options'])) + return out + +def editTypeMultiRadio(input, el): + #### so, see if string list. If so, + #items = options = input['options'].split(',') + out=[] + options = input['options'].split(',') + #print('# options, multiradio {} - {}'.format(el, options)) + if len(input['options'])>0: + out.append(' #set($list{}={})'.format(input['dataName'],options)) + out.append(' #renderfmStringMultiRadio("{}" $item $list{} $vr)'.format(el, input['dataName']) ) + + return out + + +def editTypeRadio(input, el): + #### so, see if string list. If so, + #items = options = input['options'].split(',') + out=[] + options = input['options'].split(',') + if len(input['options'])>0: + out.append(' #set($list{}={})'.format(input['dataName'],options)) + out.append(' #renderfmStringRadio("{}" $item $list{} $vr)'.format(el, input['dataName']) ) + else: + out.append(' #fmRadioYesNo("{}" $item "0" $vr)'.format(el)) + return out + +def editTypeDate(el): + return '#fmDateBox("{}" $item $vr)'.format(el) + #return '#xdatDatetBox("{}" $item $vr $years)'.format(el) + + +def editTypeDateTime(el): + dtnum = el.replace(':','').replace('/','') + return '#fmDateTimeBox("{}" $item $vr "{}")'.format(el, dtnum) + #return '#fmDateTimeLocalBox("{}" $item $vr )'.format(el) + + +def editTypeTime(el): + dtnum = el.replace(':','').replace('/','') + return '#fmTimeBox("{}" $item $vr "{}")'.format(el, dtnum) + + + +def editTypeDropdown(input,el): + + out = [] + # should be comma del by the time you get here + + options = [] + print('#### DROPDOWN {} - {}'.format(input['inputDataType'], input['options'])) + #RANGE SAVED AS STRING + if input['inputLen']>0: + # if inputlen, then save as int. + for i in range(0,int(input['inputLen']) + 1 ): + options.append("{}".format(i)) + out.append(' #set($list{}={})'.format(input['dataName'],options)) + out.append(' #renderfmIntegerDropdown( "{}" $item $list{} $vr)'.format(el,input['dataName'])) + elif 'tring' in input['inputDataType']: + if ',' not in input['options'] and '-' in input['options']: + #saved as string + options = [] + opts_range = input['options'].split('-') + + try: + min_opt = int(opts_range[0]) + max_opt = int(opts_range[1]) + for i in range(min_opt, max_opt + 1): + options.append("{}".format(i)) + except ValueError as ve: + options.append(input['options']) + + + else: + options = input['options'].split(',') + out.append(' #set($list{}={})'.format(input['dataName'],options)) + if "default" in input: + out.append(' #renderfmStringDropdown("{}" $item $list{} "{}" $vr)'.format(el,input['dataName'],input['default'])) + else: + out.append(' #renderfmStringDropdown("{}" $item $list{} "" $vr)'.format(el,input['dataName'])) + else: + options = input['options'].split(',') + #out.append(' #renderfmStringDropdown("{}" $item $list{} $vr)'.format(el,input['dataName'])) + out.append(' #set($list{}={})'.format(input['dataName'],options)) + out.append(' #renderfmScalarPropertySelect("{}" $item $list{} $vr)'.format(el,input['dataName'])) + return out + + + + + + +def editTypeBranch(input, el, section): + out = [] + + tog_name = (input['branchingquestion'].split(':')[1]).replace('/', '').replace('_', '') + panel_name = get_panel_name(input, tog_name) + if 'start' in section: + out.append('') + out.append(' #set (${} = $!item.getProperty("{}"))'.format(tog_name, input['branchingquestion'])) + out.append(''.format(panel_name, tog_name, input['branchingvalue'])) + else: + out.append('
    ') + out.append('') + return out + +def editBranchToggle(input, el): + #generic macro - fmGenericStringToggle $name $item $items $default $panel $vals $vr) + # numbers each value + iType = input['inputFormType'] + out =[] + tog_name = (el.split(':')[1]).replace('/', '').replace('_', '') + + options = input['options'].split(',') + vals = ','.join(input['branchVals']) + + if 'default' not in input: + default = '' + else: + default = input['default'] + + if 'ynredcap' in iType: + out.append(' #fmRedcapBoolToggle("{}" "{}" "panel{}" "{}" $vr)'.format(el, default, tog_name, vals)) + + else: + out.append(' #set($list{}={})'.format(input['dataName'], options)) + out.append(' #fmGenericStringToggle("{}" $item $list{} "{}" "panel{}" "{}" $vr)'.format(el, input['dataName'], default, tog_name, vals)) + print('TOGGLE #fmGenericStringToggle("{}" $item $list{} "{}" {} "{}" $vr)'.format(el,input['dataName'],default,tog_name,vals)) + #time.sleep(2) + #os.exit(0) + return out + + +def editTogglePanel(input,el): + + out =[] + iType = input['inputFormType'] + if 'multipanel' in iType: + #count number of yes, then split later.... + opts = input['options'].replace(';','').replace(',','').replace('=','').rstrip() + qs = opts.split('N') + #print('options: {} '.format(qs)) + yes_qs = int(qs[0].replace('Y','')) + no_qs = int(qs[1].replace('N','')) + #print('{} y:{} n:{} '.format(qs, yes_qs, no_qs)) + tog_name = '{}{}'.format(input['formRef'], input['dataName']) + if input['subName'] != '': + tog_name = '{}{}'.format(tog_name, input['subName']) + el = '{}/{}'.format(el, input['subName']) + + panel_name = get_panel_name(input, tog_name) + + #("toggle type: element:{} - {}".format(el, input)) + out.append('') + out.append(' '.format(input['question'])) + out.append(' ') + out.append('') + + return out + + + +def cleanForm(out): + ln = 0 + for line in out: + if ln > 0: + if out[ln] == out[ln-1]: + if '' in out[ln]: + del out[ln] + ln = ln +1 + return out \ No newline at end of file diff --git a/builder/html_vm_report.py b/builder/html_vm_report.py new file mode 100644 index 0000000..958c974 --- /dev/null +++ b/builder/html_vm_report.py @@ -0,0 +1,288 @@ +def get_panel_name(input, tog_name): + panel_name = 'panel{}'.format(tog_name) + if 'panelNum' in input: + panel_name = 'panel{}{}'.format(tog_name, input['panelNum']) + return panel_name + +def htmlTextString(input,el, out): + iType = input['inputFormType'] + # print('Test Report: {}'.format(input)) + if 'subheader' in iType: + print('REPORT-SUBHEADER: {} - {} '.format(input['question'], input['inputFormType'])) + out.extend(reportTypeSubHeader(input)) + elif 'eader' in iType: + print('REPORT-HEADER: {} - {} '.format(input['question'], input['inputFormType'])) + out.extend(reportTypeHeader(input)) + elif 'aragraph' in iType: + print('REPORT-PARAGRAPH: {} - {} '.format(input['question'], input['inputFormType'])) + out.extend(reportTypeParagraph(input)) + + +def htmlInputReportStandard(input,el, out): + dType = "" + if input['inputDataType']: + dType = input['inputDataType'] + iType = input['inputFormType'] + if 'text' in input['schemaInputType']: + htmlTextString(input, el, out) + + else: + if 'branchingquestion' in input: + # only show if.... + out = reportTypeBranch(input, out) + else: + out.append('') + + out.append(''.format(input['question'])) + + out.append('') + out.append('') + +def htmlInputReport(input,schema,formRef, subform): + toggle_panels = ['ynuo','yn','ny','ynu', 'multipanel'] + out =[] + + el='{}:{}/{}'.format(schema,formRef,input['dataName']) + if subform: + #must be subform, as includes main element: + el = '@BASE_SCHEMA@/{}'.format(input['dataName']) + + iType = input['inputFormType'] + + if 'x' in input['hide']: + print('Skipping {} - {} - hidden datatype'.format(input['dataName'], input['inputFormType'])) + elif iType in toggle_panels: + out.extend(reportTogglePanel(input,el)) + elif len(input['children']) > 0: + elsub='{}/{}'.format(el,input['subName']) + htmlInputReportStandard(input, elsub, out) + for i in input['children']: + print('REPORT-test: {}'.format(i)) + elsub ='{}/{}'.format(el,i['subName']) + if 'subheader' in i['inputFormType']: + print('SUB: REPORTSUBHEADER {}'.format(i['question'])) + out.extend(reportTypeSubHeader(i)) + elif 'eader' in i['inputFormType']: + print('SUB: REPORTHEADER {}'.format(i['question'])) + out.extend(reportTypeHeader(i)) + elif 'aragraph' in i['inputFormType']: + print('SUB: REPORT PARAGRAPH {}'.format(i['question'])) + out.extend(reportTypeParagraph(i)) + elif i['inputFormType'] in toggle_panels: + out.extend(reportTogglePanel(i,elsub)) + else: + htmlInputReportStandard(i, elsub, out) + out.append('') + else: + htmlInputReportStandard(input, el, out) + + + return out + +def reportTypeHeader(input): + head=[] + + head.append('') + head.append(''.format(input['question'])) + head.append(' ') + head.append('') + + return head + + + +def reportTypeSubHeader(input): + head=[] + + head.append('') + head.append(''.format(input['question'])) + head.append(' ') + head.append('') + + return head + + +def reportTypeBranch(input, out): + tog_name = input['branchingquestion'].replace('/', '').replace(':', '').replace('_', '') + out.append(' #set (${} = $!item.getProperty("{}"))'.format(tog_name, input['branchingquestion'])) + out.append(''.format(tog_name, input['branchingvalue'])) + return out + +def reportTypeParagraph(input): + out=[] + + if 'branchingquestion' in input: + out = reportTypeBranch(input, out) + else: + out.append('') + out.append(''.format(input['question'])) + out.append('') + + return out + +def reportTypeSubform(input, el): + #format @BASE_ELEMENT@ @SUBFORM@ - repalced later on + out = [] + out.append('@{}@ @{}@'.format(el, input['options'])) + return out + +def reportTypeDateTimeBox(el): + return '#showDateTime("{}" $item)'.format(el) + +def reportTypeDateBox(el): + return '#showDate("{}" $item)'.format(el) + + +def reportTypeTickBox(el): + + return '#showfmBoolean("{}" $item)'.format(el) + +def reportTypeText(el): + + return '$!item.getStringProperty("{}")'.format(el) + +def reportTypeRadio(input, el): + if len(input['options'])>0: + return ' $!item.getProperty("{}")'.format(el) + else: + return '#showfmBoolean("{}" $item)'.format(el) + + +def reportTypeDropdown(input,el): + + out = [] + options = [] + + + if input['inputLen'] > 0: + # then take from length + for i in range(0, int(input['inputLen']) + 1): + options.append("{}".format(i)) + else: + if ',' not in input['options'] and '-' in input['options']: + #saved as string + opts_range = input['options'].split('-') + min_opt= int(opts_range[0]) + max_opt= int(opts_range[1]) + for i in range(min_opt, max_opt + 1): + options.append("{}".format(i)) + else: + options = input['options'].split(',') + + + + print(options,el,input['dataName']) + if 'nt' not in input['inputDataType']: + out.append(' $!item.getProperty("{}")'.format(el) ) + else: + out.append(' #set($list{}={})'.format(input['dataName'],options)) + out.append(' #showScalarIntProperty("{}" $item $list{} )'.format( el,input['dataName'])) + return out + + +def reportTogglePanel(input,el): + + out =[] + iType = input['inputFormType'] + if 'multipanel' in iType: + #count number of yes, then split later.... + opts = input['options'].replace(';','').replace(',','').replace('=','') + qs = opts.split('N') + print('options: {} '.format(qs)) + yes_qs = int(qs[0].replace('Y','')) + no_qs = int(qs[1].replace('N','')) + print('{} y:{} n:{} '.format(qs, yes_qs, no_qs)) + + tog_name = '{}{}'.format(input['formRef'],input['dataName']) + if input['subName'] != '': + tog_name = '{}{}'.format(tog_name,input['subName']) + el = '{}/{}'.format(el, input['subName']) + + panel_name = get_panel_name(input, tog_name) + print("toggle type: element:{} - {}".format(el, input)) + out.append('') + out.append(''.format(input['question'])) + out.append(' ') + + return out diff --git a/builder/loader_redcap.py b/builder/loader_redcap.py new file mode 100644 index 0000000..bc0d0dd --- /dev/null +++ b/builder/loader_redcap.py @@ -0,0 +1,565 @@ +import csv +import os +import sys +import time + + +class LoaderRedcap(object): + + + #NOTES: + # some are required BUT only if showing - ie, branched questions. So skip required if from branched + + + def __init__(self, configData): + + self.errors = [] + self.log =[] + self.configData = configData + self.allowedFormTypes = ['text','dropdown','radio', 'multiradio', 'calc','tickbox','yn','ny','ynu','ynuo','textbox', 'textarea','date', 'header', 'subheader', 'paragraph', 'insertparagraph', 'datetime'] + self.allowedDataTypes = ['string', 'bool', 'float', 'int', 'header', 'complex', 'date', 'dateTime'] + self.allowedExtensions = ['subject', 'image', 'session'] + self.allowedDropDownTypes = ['int', 'string'] + self.toggle_panels = ['ynuo','yn','ny','ynu'] + self.textFormTypes = ['header', 'subheader', 'paragraph', 'text'] + + + def hasError(self): + return len(self.errors) + + def errors(self): + return self.errors + + def dos2unix(self, path): + #have to convert + os.system("find ./redcap_dict/* -name *.csv -type f -exec dos2unix -u {} \; ") + + def filesList(self,path): + out = [] + for file in os.listdir(path): + if file.endswith('.csv') and '~' not in file: + out.append(file) + + return out + + def loadRedcap(self,path,fileName): + + #schema name MUST be filename?? + # take as parmetrs? + + out = {'schema': '', 'order': [], 'forms': dict()} + + + schemaName = fileName.replace(".csv", "").replace("_","").replace("-","") + out['schema'] = schemaName + out['restrict_to_project'] = '' + + + data = dict() + questions = dict() + qs = [] + strId = "" + + fullPath = ('{}/{}' .format(path,fileName)) + + + + #windows-1252?? convert beforehand + + with open(fullPath, mode='r', encoding='windows-1252') as redcap_csv: + csv_reader = csv.DictReader(redcap_csv) + print(f'Column names are ', csv_reader.fieldnames) + line_count = 0 + prev_form_name = "" + + try: + + for row in csv_reader: + + line_count += 1 + #ignore images + if "img src" not in row["Field Label"]: + + print('#########') + #print(row) + if row["Form Name"] != prev_form_name: + ## complete previous + if len(strId) > 0: + for q in qs: + if 'branch_logic' in q: + qs = self.cleanBranching(qs, q) + out['forms'][strId]['inputs'] = self.sortByDataName(qs) + qs = [] + + # The same.... ID and Name + prev_form_name = row["Form Name"] + strId = row["Form Name"] + ## Bean can be tooo long + full_length = '{}{}'.format(schemaName, strId.replace("_"," ")) + if len(full_length) > 68: + self.errors.append("Error reading rows: File {} Form {} is too long - {} chars. Replace Form Name in csv or shorten filename".format(schemaName, strId, len(full_length))) + + + formName = row["Form Name"].replace("_"," ").lower() # the form title + formRef = row["Form Name"].replace("_","").replace(' ', '').lower() # database name + ext = 'subject' + desc = row["Form Name"] + + strId = strId.replace(' ', '') + data = dict() + data['strId'] = strId + data['name'] = formName.replace("'","").replace('-', '') + data['ref'] = formRef.replace(' ', '').replace("'","").replace('-', '').lower() + + data['inputs'] = [] + data['ext'] = ext.lower() + data['desc'] = desc + data['schema'] = schemaName + + out['order'].append(strId) + out['forms'][strId] = data + print("Found form:{} - {}".format(strId, out['forms'][strId]['name'])) + + + ## read questions in + section_header = self.cleanCellValue(row["Section Header"]).replace('align:center;', 'align:left;') + # add paragraph for section_header - ignoring any images + if len(section_header) > 1: + questions = dict() + questions['parent'] = '' + questions['children'] = [] + questions['schema'] = schemaName + questions['formRef'] = data['ref'] + + questions['question'] = section_header + questions['dataName'] = '' + questions['subName'] = '' + questions['inputFormType'] = 'paragraph' + questions['inputDataType'] = '' + questions['inputLen'] = 0 + questions['required'] = '0' + questions['schemaInputType'] = 'text' + questions['hide'] = '' + qs.append(questions) + + questions = dict() + questions['question'] = self.cleanCellValue(row["Field Label"]).replace('align:center;', 'align:left;') + + questions['dataName'] = self.cleanCellValue(row["Variable / Field Name"]).replace("_","") + fieldType = self.cleanCellValue(row["Field Type"]) + required = self.cleanCellValue(row["Required Field?"]) + annotation= self.cleanCellValue(row["Field Annotation"]) + #print(row["Choices, Calculations, OR Slider Labels"]) + + opts = self.cleanDropDownOptions(row["Choices, Calculations, OR Slider Labels"]) + #print(opts) + + questions['rawopts'] = row["Choices, Calculations, OR Slider Labels"] + + questions['inputLenType'] = self.cleanCellValue(row["Text Validation Type OR Show Slider Number"]) + inputLenMin = self.cleanCellValue(row["Text Validation Min"]) + inputLenMax = self.cleanCellValue(row["Text Validation Max"]) + questions['inputLen'] = 0 + if inputLenMax != '': + if 'int' in questions['inputLenType']: + questions['inputLen'] = int(inputLenMax) + if 'numb' in questions['inputLenType']: + questions['inputLen'] = float(inputLenMax) + # IF date_dmy, or time then NOT text as shown in field type + + + questions['inputFormType'], questions['inputDataType'] = self.convertFieldType(fieldType, row["Choices, Calculations, OR Slider Labels"], questions['inputLenType']) + + + if 'multiradio' in questions['inputFormType']: + num = 1 + for opt in opts.split(','): + print('test, option:',opt) + questions = dict() + questions['dataName'] = self.cleanCellValue(row["Variable / Field Name"]).replace("_","") + questions['inputDataType'] = 'boolean' + questions['inputFormType'] = 'tickbox' + questions['question'] = '{} - {}'.format(self.cleanCellValue(row["Field Label"]).replace('align:center;', 'align:left;'), opt) + questions['inputLen'] = 0 + questions['required'] = '0' + questions['hide'] = '' + questions['options'] = '' + + questions['parent'] = '' + questions['children'] = [] + questions['schema'] = schemaName + questions['formRef'] = data['ref'] + # does nto exist in redcap + questions['subName'] = num + questions['schemaInputType'] = self.schemaInputType(questions['inputFormType'], questions['inputDataType']) + num += 1 + + qs.append(questions) + else: + + questions['parent'] = '' + questions['children'] = [] + questions['schema'] = schemaName + questions['formRef'] = data['ref'] + #does nto exist in redcap + questions['subName'] = "" + + questions['required'] = '0' + #Skip if conditional required - ie, required if X is selected + if 'y' in required and not len(self.cleanCellValue(row["Branching Logic (Show field only if...)"])) > 0: + questions['required'] = '1' + + questions['options']= opts + questions['hide'] = '' + + if 'HIDE' in annotation: + questions['hide'] = 'x' + if 'DEFAULT' in annotation: + questions['default'] = annotation.replace('@DEFAULT','').replace('=','').replace('"','') + defaultopts = row["Choices, Calculations, OR Slider Labels"].split('|') + for op in defaultopts: + if questions['default'] in op: + op = op.translate({ord(c): "" for c in "!@#$%^&*()[]{};:./<>?\`~-=_+"}) + questions['default'] = " ".join(op.split()[1:]) + + print('Default found: {}'.format(questions['default'])) + if 'aken' in defaultopts: + sys.exit() + + questions['schemaInputType'] = self.schemaInputType(questions['inputFormType'], questions['inputDataType']) + #other + + if len(self.cleanCellValue(row["Branching Logic (Show field only if...)"])) > 0: + print('Only show if - found: ', row["Branching Logic (Show field only if...)"]) + questions['branch_logic'] = row["Branching Logic (Show field only if...)"] + + + if len(self.cleanCellValue(row["Field Note"])) > 0: + questions['note'] = self.cleanCellValue(row["Field Note"]) + questions['question'] = '{} ({})'.format(questions['question'], questions['note']) + + qs.append(questions) + + if not questions['question']: + print('No question found! form name:{} section_header:{}'.format(row["Form Name"], section_header)) + continue + + + #in case there is no extra line, then skips... + if len(qs) > 0: + for q in qs: + if 'branch_logic' in q: + qs = self.cleanBranching(qs, q) + out['forms'][strId]['inputs'] = self.sortByDataName(qs) + qs = [] + except Exception as e: + self.errors.append("Error - last form {} line {} {} - reading rows: {}".format(prev_form_name,line_count,questions['question'],e)) + print(self.errors()) + sys.exit(0) + + + for x in out['forms']: + self.checkForm(out['forms'][x]) + + return out + + + + def checkInput(self,formName,i): + + # check form type + if i['inputFormType'] not in self.allowedFormTypes : + self.errors.append('Form {} Question : {} ,Invalid Form Type {} '.format(formName, i['question'],i['inputFormType'])) + + # check data type + if (i['inputDataType'] != '') and (i['inputDataType'] not in self.allowedDataTypes) : + self.errors.append('Form {} Question : {} ,Invalid Data Type {} '.format(formName, i['question'],i['inputDataType'])) + + + def convertFieldType(self, fieldType, opts, lenType): + + #Lentype Has to come first, as string in redcap + if 'datetime' in lenType: + return 'datetime','string' + elif 'date' in lenType: + return 'date','date' + elif 'time' in lenType: + return 'time','string' + elif 'number' in lenType: + return 'textbox','float' + elif 'integer' in lenType: + return 'textbox','integer' + + if 'text' in fieldType: + return 'textbox','string' + if 'calc' in fieldType: + #datediff and sums... + if 'if(' in opts and 'datediff' in opts: + # just do text box for now + return 'calcifdd', 'string' + if 'if(' in opts and 'sum' in opts: + # just do text box for now + return 'calcifsum', 'string' + if 'if(' in opts: + # just do text box for now + return 'calcif', 'string' + elif 'datediff' in opts: + return 'calcdd', 'float' + elif 'sum' in opts: + return 'calcsum', 'float' # need to make hidden in edit form. Usually date difference + else: + #just do text box for now + return 'calc', 'string' + if 'yesno' in fieldType or 'truefalse' in fieldType: + return 'tickbox', 'boolean' + if 'notes' in fieldType: + return 'textarea', 'string' + if 'radio' in fieldType: + return 'radio', 'string' + if 'descriptive' in fieldType: + return 'paragraph', '' + if 'dropdown' in fieldType: + return 'dropdown', 'string' + if 'checkbox' in fieldType: + return 'multiradio', 'string' + + + def schemaInputType(self,formType,dataType): + iType='none' + if formType.lower() in self.textFormTypes: + iType = 'text' + return iType + else: + iType = 'string' if not dataType else dataType + + + if 'ool' in dataType: + iType='boolean' + + elif 'int' in dataType: + iType='integer' + elif 'loat' in dataType: + iType='float' + + elif formType == 'date': + iType = 'date' + elif formType == 'datetime': + iType = 'string' + if formType.lower() in self.textFormTypes: + iType == 'text' + if formType == 'dropdown' and not dataType: + #only assumes int if specifies int + iType = 'string' + + if 'tickbox' in formType: + iType='boolean' + + return iType + + def setLength(self,data): + if not data: + return 0 + else: + try: + return int(data) + except Exception as e: + print("not an int - {} - floats not restictable? {}".format(data)) + return '' + + def cleanBranching(self, qs, question ): + + ## example: in 'Branching Logic (Show field only if...) + # [c_section] = '1' - NOT in order, can be anywhere. Also can be muitple OR or refer from another form + # panel name: [original question - dataname][panel num] + # generic macro - fmGenericStringToggle $name $item $items $default $panel $vals $vr) + # for each value, numbers it. Then on each click has a panel. + # issue -it appears not in order - the original question can be after + + # only do if simple - so only one + + condition = question['branch_logic'] + if len(condition.split('=')) == 2: + a = condition.split('=') + #val = self.cleanDropDownOptions(a[1])#.replace("'","").replace('"','').replace(" ","") + val = a[1].replace("'","").replace('"','').replace(" ","") + + + # excludes referencs to other forms + if len(a[0].split(']')) == 2: + #el is branching question + el = a[0].replace('[','').replace(']','').replace('_','').replace(" ","") + print(a, val,el) + if ')' not in el: + for q in qs: + #print(q['dataName']) + if q['dataName'] == el: + print("CLEAN BRANCHING: FOUND EL {} question: {} val: -{}- schme type: {}".format(el, q['dataName'], val, q['schemaInputType'])) + if 'ool' in q['schemaInputType']: + val = val.replace(" ","") + print("FOUND BOOLEAN val {}".format(val)) + #ynredcap - can open both if 0 or 1 + q['inputFormType'] = 'ynredcap' + if 'branchNum' in q: + print(q['branchNum']) + q['branchNum'] += 1 + else: + q['branchNum'] = 1 + + if 'branchVals' not in q: + q['branchVals'] ='' + if '0' in val: + question['panelNum'] = '{}'.format(q['branchNum']) + q['branchVals'] = '{}{}'.format(q['branchVals'], 'N') + else: + question['panelNum'] = '{}'.format(q['branchNum']) + q['branchVals'] = '{}{}'.format(q['branchVals'], 'Y') + + elif 'rawopts' in q: + for option in q['rawopts'].split('|'): + + if '{},'.format(val) in option: + if 'branchVals' not in q: + q['branchVals'] = [] + # remove the option number (format, example '1, yes') + val = " ".join(option.split()[1:]).replace(',','') + print("FOUND conditional_val: {} option: {}".format(val, option)) + q['branchVals'].append(val.translate ({ord(c): "" for c in "!@#$%^&*()[]{};:./<>?\`~-=_+"})) + if 'branchNum' in q: + print(q['branchNum']) + q['branchNum'] += 1 + else: + q['branchNum'] = 1 + + question['panelNum'] = q['branchNum'] + + + el = '{}:{}/{}'.format(question['schema'], question['formRef'], el) + + question['branchingquestion'] = el + question['branchingvalue'] = val.translate ({ord(c): "" for c in "!@#$%^&*()[]{};:./<>?\`~-=_+"}) + if 'branchNum' in q: + print("BRANCHING CLEANING: bracnch found in: {} element {} PanelNUm: {} value {} ".format(question['dataName'], el,question['branchNum'], question['branchingvalue'])) + #time.sleep(2) + ## if redoing, then replace existing: + for q in qs: + if q['dataName'] == question['dataName']: + q['branchingquestion'] = question['branchingquestion'] + if 'panelNum' in q: + print('panel num:',question['panelNum']) + q['panelNum'] = question['panelNum'] + q['branchingvalue'] = question['branchingvalue'] + + return qs + + + + + + def cleanCellValue(self,data): + + if (not data or data == None): + + return '' + data = data.strip().encode('ascii', 'ignore') + data_str=data.decode() + + return data_str + + # return '' if not data else data.strip() + + def cleanDropDownOptions(self,o): + + # in redcap split by | - need to replace with , + bstr= '{}'.format(o) + # have to replace as | sepreated, and need to change to comma seperated + bstr = bstr.replace(",", " ") + + bstr = bstr.translate ({ord(c): "" for c in "!@#$%^&*()[]{};:./<>?\`~-=_+"}) + + cstr = '' + first=True + for i in bstr.split('|'): + if first: + cstr = " ".join(i.split()[1:]) + #print('test opts in redcap: 1st:', i, ' cstr:', cstr) + first = False + #sys.exit(0) + else: + cstr = '{},{}'.format(cstr, " ".join(i.split()[1:])) + #cstr = cstr[2:] + + return cstr + + def sortByDataName(self,inputs): + + temp = {'order': [], 'inputs' : {}} + out = [] + #urgh, fix to stop recounting repeated paragraphs/headers + textnum = 555 + for i in inputs: + + dataName = i['dataName'] + if i['inputFormType'] in self.textFormTypes: + dataName = str(textnum) + textnum = textnum + 1 + + if dataName in temp['inputs'].keys(): + i['parent'] = dataName + temp['inputs'][dataName]['children'].append(i) + else: + temp['inputs'][dataName] = i + temp['order'].append(dataName) + # out[i['dataName']] = [i] + + for o in temp['order']: + out.append(temp['inputs'][o]) + #print('------------###########-----------') + #print('{}'.format(out)) + #print temp['order'] + return out + + #TODO needs update + def checkForm(self,form): + + + + + if form['ext'] not in self.allowedExtensions : + self.errors.append('Form {} Invalid Extension {} '.format(form['name'], form['ext'])) + # already checked if no inputs + # reset list for questions + qs = [] + + for i in form['inputs']: + print('CHECKDATA: {}'.format(i)) + # yn do not NEED subnames as already exist. (completed, reasonnotable) + #if 'yn' in i['inputFormType'] and len(i['subName'])<1: + # self.errors.append('Form {} has missing subname: {} - {} - {}'.format(form['name'], i['question'],i['dataName'], i['inputFormType'])) + + + temp = i['question'].replace(" ",'') + i['dataName'] + if temp in qs and i['inputFormType'] not in self.textFormTypes: + self.errors.append('Form {} has Dupe input {} - {}'.format(form['name'], i['question'],i['dataName'])) + else: + qs.append(temp) + # not for redcap, not doing subtypes + #if len(i['children']) < 1 and i['inputFormType'] in self.toggle_panels: + # self.errors.append('Form {}: Question: {} - Panel Toggle Type {} has no subtypes! '.format(form['name'], i['question'],i['dataName'])) + + + #TODO check for group inputs with no subname + + def printForms(self,forms): + + # already checked if no inputs + for f in forms: + print('Form {}'.format(forms[f]['name'])) + print('schema {}'.format(forms[f]['schema'])) + + for i in forms[f]['inputs']: + print(' -- input : question {} : Type {}'.format(i['question'], i['inputFormType'])) + if len(i['children']) > 0: + for c in i['children']: + print(' -- input : question {} : Type {}'.format(c['question'],c['inputFormType'])) + + diff --git a/builder/loader_xlsx.py b/builder/loader_xlsx.py new file mode 100644 index 0000000..4d3c825 --- /dev/null +++ b/builder/loader_xlsx.py @@ -0,0 +1,486 @@ +import os + + +from openpyxl import load_workbook + +class LoaderXlsx(object): + + def __init__(self, configData): + + self.errors = [] + self.log =[] + self.configData = configData + self.allowedFormTypes = ['subform','text','dropdown','radio', 'tickbox','checkbox','yn','ny','ynu','ynuo','multipanel', 'textbox', 'textarea','date', 'header', 'subheader', 'paragraph', 'datetime', 'time'] + self.allowedDataTypes = ['string', 'bool', 'float', 'int', 'header', 'complex', 'date','dateTime'] + self.allowedExtensions = ['subform','subject', 'image', 'session'] + self.allowedDropDownTypes = ['int', 'string'] + self.toggle_panels = ['ynuo','yn','ny','ynu', 'multipanel'] + self.textFormTypes = ['header', 'subheader', 'paragraph', 'text'] + + def hasError(self): + return len(self.errors) + + def errors(self): + return self.errors + + + def filesList(self,path): + out = [] + for file in os.listdir(path): + if file.endswith('.xlsx') and '~' not in file: + out.append(file) + + return out + + def loadExcel(self,path,fileName): + + fullPath = ('{}/{}' .format(path,fileName)) + wb = load_workbook(fullPath) + out = {'schema': '', 'order' :[] , 'forms':dict()} + + formnames = [] # check for duplicates + subformnames = [] # check for duplicates + + out['foundFormDetails'] = False + + for ws in wb: + + if ws.title == 'FormDetails': + out['foundFormDetails'] = True + schemaName = ws['B1'].value.replace(' ', '').replace("'","").replace('-', '') + out['schema'] = schemaName.replace(' ', '').replace("'","").replace('-', '') + + + out['restrict_to_project'] = '' + if ws['A2'].value: + if 'Restrict to Project' in ws['A2'].value: + if ws['B2'].value: + restricted_proj = ws['B2'].value.replace(' ', '').replace("'", "").replace('-', '') + out['restrict_to_project'] = restricted_proj.replace(' ', '').replace("'", "").replace('-', '') + + + for x in range(3,100): + + data = dict() + + strId = self.cleanCellValue(ws['A'+str(x)].value) + if 'Form' in strId: + formName = self.cleanCellValue(ws['B'+str(x)].value) + formRef = self.cleanCellValue(ws['C'+str(x)].value) + ext = self.cleanCellValue(ws['D'+str(x)].value) + desc = self.cleanCellValue(ws['E'+str(x)].value) + if 'ession' in '{}'.format(ext).lower(): + ext = 'image' + + + if (strId) and (strId.startswith('Form')): + + if not formName.strip() : + continue + else: + + if not formName.strip() : + self.errors.append('No Form ref, cell C is empty') + continue + #replace spaces + strId = strId.replace(' ', '') + data['strId'] = strId + data['name'] = formName.replace("'","").replace('-', '') + data['ref'] = formRef.replace(' ', '').replace("'","").replace('-', '') + if not self.valid_name(data['ref']): + self.errors.append( + 'Form {} is not valid - {} - Check Form! '.format( + strId, data['ref'] )) + + data['inputs'] = [] + data['ext'] = ext.lower() + data['desc'] = desc + data['schema'] = schemaName + + out['order'].append(strId) + out['forms'][strId] = data + + # check for duplicates + + if data['ref'] in formnames and len(data['ref']) > 0: + self.errors.append( + 'Duplicate! Form {} is a duplicate: Check Form! '.format( + strId )) + formnames.append(data['ref']) + if 'subform' in data['ext']: + #subformnames + subformnames.append(data['ref']) + + + if not out['foundFormDetails']: + self.errors.append('Page FormDetails page not found!!! Check CSV') + + if len(out['schema']) < 1: + self.errors.append('Form {} Schema too short or missing: {} '.format(out['name'], out['schema'])) + + + for x in out['forms']: + inputs = self.addInputs(x,wb,schemaName,out['forms'][x]) + + if(len(inputs)) == 0: + self.errors.append('Form {} has no inputs'.format(strId)) + out['forms'][x]['inputs'] = self.sortByDataName(inputs) + + #self.printForms(out['forms']) + + for x in out['forms']: + self.checkForm(out['forms'][x],subformnames) + + return out + + def addInputs(self,strId,wb,schema,form): + textnum = 1000 + out = [] + complex_type=False + + + for ws in wb: + + if ws.title.replace(' ','') == strId: + + datatypes = [] # check for duplicates + for x in range(2,500): + data =dict() + + question = self.cleanCellValue(ws['A'+str(x)].value) + dataName = self.cleanCellValue(ws['B'+str(x)].value).replace(' ','') + subName = self.cleanCellValue(ws['C'+str(x)].value).replace(' ','') + formType = self.cleanCellValue(ws['D'+str(x)].value).replace(' ','') + dataType = self.cleanCellValue(ws['E'+str(x)].value).replace(' ','') + inputLen = self.setLength(ws['F'+str(x)].value) + opts = self.cleanDropDownOptions(self.cleanCellValue(ws['G'+str(x)].value)) + hide = self.cleanCellValue(ws['H'+str(x)].value).replace(' ','') + required = self.cleanCellValue(ws['I' + str(x)].value).replace(' ', '') + + if not question : + break + + if not dataName: + if formType.lower() not in self.textFormTypes: + self.errors.append('Form {}, Input {} : has no Data Name'.format(strId,question)) + if not self.valid_name(dataName): + self.errors.append('Form {}, Input {} : data name {} not valid - no special characters'.format(strId,question, dataName)) + + if dataName == "date": + # date not permiitted as dataname, creates erros + dataName = 'date{}'.format(schema) + + if formType in self.textFormTypes: + subName = str(textnum) + textnum = textnum + 1 + if 'heckbox' in formType: + formType = 'tickbox' + + # need to loop for multiradio... REMOVED - just do checkbox list.... + if 'multiradio' in formType: + mr_opts = opts.split(',') + mr_num=0 + for mr_opt in mr_opts: + subName = '{}{}{}'.format(dataName, subName, mr_opt) + if mr_num < 1: + subName = '{}xstart'.format(subName) + if mr_num == len(mr_opts): + subName = '{}xend'.format(subName) + mr_num += 1 + + + + data['parent'] = '' + data['children'] = [] + data['schema'] = schema + data['formRef'] = form['ref'] + + data['question'] = question + data['dataName'] = dataName + data['subName'] = subName + data['inputFormType']= formType.lower() + data['inputDataType']= dataType.lower() + data['inputLen']= self.setLength(inputLen) + data['required'] = "0" + if 'y' in required.lower() or 'x' in required.lower(): + data['required'] = "1" + + + # check for duplicates + dt = '{}{}'.format(dataName, subName) + if dt in datatypes and len(dataName) > 0: + self.errors.append('Duplicate! Form {}, Input {} Dataname: {} Subname: {}: Check Form! '.format(strId,question, dataName, subName)) + datatypes.append(dt) + + + data['hide']= hide + data['options']= opts + + if data['inputFormType'] == 'dropdown' and not data['inputDataType']: + #only assumes int if specifies int + data['inputDataType'] = 'string' + + # SHCEMA INPUT TYPE - if header, paragraph etc, schema type = text and is ignored + #print("{}{}".format(data['inputFormType'], data['inputDataType'])) + data['schemaInputType'] = self.schemaInputType(data['inputFormType'], data['inputDataType'],data['options']) + if 'datetime' in formType: + #? any need for this? + data['inputDataType'] = 'dateTime' + #print('LOADER DATA: {} '.format(data)) + + self.checkInput(form['name'],data) + out.append(data) + else: + data['parent'] = '' + data['children'] = [] + data['schema'] = schema + data['formRef'] = form['ref'] + + data['question'] = question + data['dataName'] = dataName + data['subName'] = subName + data['inputFormType'] = formType.lower() + data['inputDataType'] = dataType.lower() + data['inputLen'] = self.setLength(inputLen) + data['required'] = "0" + if 'y' in required.lower() or 'x' in required.lower(): + data['required'] = "1" + + # check for duplicates + dt = '{}{}'.format(dataName, subName) + if dt in datatypes and len(dataName) > 0: + self.errors.append( + 'Duplicate! Form {}, Input {} Dataname: {} Subname: {}: Check Form! '.format(strId, + question, + dataName, + subName)) + datatypes.append(dt) + + data['hide'] = hide + data['options'] = opts + + if data['inputFormType'] == 'dropdown' and not data['inputDataType']: + # only assumes int if specifies int + data['inputDataType'] = 'string' + + # SHCEMA INPUT TYPE - if header, paragraph etc, schema type = text and is ignored + # print("{}{}".format(data['inputFormType'], data['inputDataType'])) + data['schemaInputType'] = self.schemaInputType(data['inputFormType'], data['inputDataType'],data['options'] ) + if 'datetime' in formType: + # ? any need for this? + data['inputDataType'] = 'dateTime' + #print('LOADER DATA: {} '.format(data)) + + #self.checkInput(form['name'], data) + out.append(data) + return out + + def checkInput(self,form,i, qs, subformnames=''): + + # check form type + if i['inputFormType'] not in self.allowedFormTypes : + self.errors.append('Form {} Question : {} ,Invalid Form Type {} '.format(form['name'], i['question'],i['inputFormType'])) + + # check data type + if (i['inputDataType'] != '') and (i['inputDataType'] not in self.allowedDataTypes) : + self.errors.append('Form {} Question : {} ,Invalid Data Type {} '.format(form['name'], i['question'],i['inputDataType'])) + + ## check to see if subforms exist, and options correct + if 'subform' == i['inputFormType'] and i['options'] not in subformnames: + self.errors.append('Form {} has subform with no exisiting type: {} - {} - {} '.format(form['name'], i['question'],i['options'], i['inputFormType'])) + + # yn do not NEED subnames as already exist. (completed, reasonnotable) + # ynuo has no subname + if 'yn' == i['inputFormType'] and len(i['subName']) < 1: + self.errors.append( + 'Form {} has missing subname: {} - {} - {}'.format(form['name'], i['question'], i['dataName'], + i['inputFormType'])) + if 'ny' == i['inputFormType'] and len(i['subName']) < 1: + self.errors.append( + 'Form {} has missing subname: {} - {} - {}'.format(form['name'], i['question'], i['dataName'], + i['inputFormType'])) + if 'multipanel' == i['inputFormType']: + opts = i['options'].split(',') + if len(i['subName']) < 1: + self.errors.append( + 'Form {} has missing subname: {} - {} - {}'.format(form['name'], i['question'], i['dataName'], + i['inputFormType'])) + if len(opts) != 2: + self.errors.append( + 'Form {} - multipanel question has wrong options format, see Wiki: {} - {} - {}'.format( + form['name'], i['question'], i['dataName'], i['options'])) + elif 'Y' not in opts[0] or 'N' not in opts[1]: + self.errors.append( + 'Form {} - multipanel question has wrong options format, see Wiki: {} - {} - {}'.format( + form['name'], i['question'], i['dataName'], i['options'])) + + temp = i['question'].replace(" ", '') + i['dataName'] + i['subName'] + if temp in qs and i['inputFormType'] not in self.textFormTypes: + self.errors.append('Form {} has duplicate input {} - {}'.format(form['name'], i['question'], i['dataName'])) + else: + qs.append(temp) + if len(i['children']) < 1 and i['inputFormType'] in self.toggle_panels: + self.errors.append( + 'Form {}: Question: {} - Panel Toggle Type {} has no subtypes! '.format(form['name'], i['question'], + i['dataName'])) + # check form type + if 'date' == i['inputFormType'] and i['inputLen'] != 0: + self.errors.append('Form {} Question : {} , Length restriction applied to date type - not valid!'.format(form['name'], i['question'],i['inputFormType'])) + + + def schemaInputType(self,formType,dataType, options): + #options should contain the subform name + + iType='none' + if formType in self.textFormTypes: + iType = 'text' + else: + iType = 'string' if not dataType else dataType + + #print('itype {} form {} data {} formtype: {}'.format(iType, formType, dataType,self.textFormTypes)) + + + if formType in self.toggle_panels: + iType = 'integer' + elif formType == 'date': + iType = 'date' + elif formType == 'datetime': + #iType = 'dateTime' + # better as string?? + iType = 'string' + + + elif formType == 'textbox' and 'int' not in dataType: + if 'loat' not in dataType: + #can be float as well. So change after + iType = 'string' + + if 'ool' in dataType: + iType='boolean' + + elif 'int' in dataType: + iType='integer' + elif 'loat' in dataType: + iType='float' + + + if formType == 'dropdown' and not dataType: + #only assumes int if specifies int + iType = 'string' + + if 'tickbox' in formType: + iType='boolean' + + if formType == 'subform': + ## need to check if existing form.... + iType=options + + return iType + + def setLength(self,data): + if not data: + return 0 + else: + return int(data) + + def cleanCellValue(self,data): + + if (not data or data == None): + + return '' + data = data.strip().encode('ascii', 'ignore') + data_str=data.decode() + + return data_str + + # return '' if not data else data.strip() + + def valid_name(selfself, dataName): + if set('[~!@#$%^&*()_+{}":;\']+$').intersection(dataName): + return False + else: + return True + + + def cleanDropDownOptions(self,o): + + a = o.split('-') + + if len(a) == 2 and ',' not in o and isinstance(a[0], int) and isinstance(a[1], int): + r = a[0] + for i in range(int(a[0]) + 1, int(a[1]) + 1): + r = r + "," + str(i) + + print(r) + return r + + bstr= '{}'.format(o) + bstr = bstr.translate ({ord(c): "" for c in "!@#$%^&*()[]{};:./?\|`~=_"}) + + #bstr = bstr.replace(" ","") + + return bstr + + def sortByDataName(self,inputs): + + temp = {'order': [], 'inputs' : {}} + out = [] + #urgh, fix to stop recounting repeated paragraphs/headers + textnum = 555 + for i in inputs: + + dataName = i['dataName'] + #### need to make so can be in panels + if i['inputFormType'] in self.textFormTypes and len(i['dataName'])<1: + dataName = str(textnum) + textnum = textnum + 1 + + if dataName in temp['inputs'].keys(): + i['parent'] = dataName + temp['inputs'][dataName]['children'].append(i) + else: + temp['inputs'][dataName] = i + temp['order'].append(dataName) + # out[i['dataName']] = [i] + + for o in temp['order']: + out.append(temp['inputs'][o]) + #print('------------###########-----------') + #print('{}'.format(out)) + #print temp['order'] + return out + + #TODO needs update + def checkForm(self,form, subformnames): + + + #print('CHECK FORMS: formanems:', subformnames) + if form['ext'] not in self.allowedExtensions : + self.errors.append('Form {} Invalid Extension {} '.format(form['name'], form['ext'])) + # already checked if no inputs + # reset list for questions + qs=[] + + for i in form['inputs']: + self.checkInput( form, i,qs, subformnames) + if len(i['children']) > 0: + for c in i['children']: + self.checkInput(form, c,qs, subformnames) + + #TODO check for group inputs with no subname + + + def printForms(self,forms): + + # already checked if no inputs + for f in forms: + print('Form {}'.format(forms[f]['name'])) + print('schema {}'.format(forms[f]['schema'])) + + for i in forms[f]['inputs']: + print(' -- input : question {} : Type {}'.format(i['question'], i['inputFormType'])) + if len(i['children']) > 0: + for c in i['children']: + print(' -- input : question {} : Type {}'.format(c['question'],c['inputFormType'])) + + diff --git a/builder/schema.py b/builder/schema.py new file mode 100644 index 0000000..1219c55 --- /dev/null +++ b/builder/schema.py @@ -0,0 +1,335 @@ +# if schmea type = text, then ignores (this includes header, subheader etc. +import errno +import os + +class SchemaHandler(object): + + def __init__(self, configData): + + self.errors = [] + self.log =[] + self.configData = configData + self.schemaName = configData['schemaName'] + self.version = configData['version'] + + self.part_plugin_path = 'src/main/resources/schemas' + + def hasError(self): + return len(self.errors) + + def errors(self): + return self.errors + + + def checkDir(self,vmFile): + + if not os.path.exists(os.path.dirname(vmFile)): + try: + os.makedirs(os.path.dirname(vmFile)) + except OSError as exc: # Guard against race condition + if exc.errno != errno.EEXIST: + raise + + def write_schema_xsd(self,plugin_name,data): + + + dir_name = '{}/plugins/{}/{}/{}'.format(self.configData['path_plugin_builder'],self.schemaName,self.part_plugin_path,self.schemaName) + schema_file = '{}/{}.xsd'.format(dir_name,self.schemaName) + + self.log.append('write schema dir : {}'.format(dir_name)) + self.log.append('write schema file : {}'.format(schema_file)) + + if not os.path.exists(dir_name): + os.makedirs(dir_name) + + #lets load our [replace me] template + fxsd = open(schema_file,"w") + for line in data: + fxsd.write(line + '\n') + fxsd.close() + + def all_data_xsd(self,data): + #print(data) + out = self.schemaHeader() + + #add all form xml elments -DO SUBFORMS FIRST + for f in data['forms']: + if 'subform' in data['forms'][f]['ext']: + print('SUBFORM', data['forms'][f]['ext']) + out.extend(self.formXsd(data['forms'][f])) + + + for f in data['forms']: + if 'subform' not in data['forms'][f]['ext']: + print('NOT SUBFORM', data['forms'][f]['ext']) + out.extend(self.formName(data['forms'][f])) + #os.exit() + + out.append(' ') + out.append(' ') + out.append(' ') + + for f in data['forms']: + if 'subform' not in data['forms'][f]['ext']: + out.extend(self.formXsd(data['forms'][f])) + + out.extend(self.end_schema()) + + return out + + def formXsd(self,form): + + out =[] + + out.extend(self.start_complex_type_named(form['ref'])) + + #not used + out.extend(self.make_form_desc(form['name'])) + if 'subform' not in form['ext']: + out.extend(self.start_complex_content()) + #not for subforms as do not extend + s = 'imageAssessorData' + if 'ubj' in form['ext']: + s = 'subjectAssessorData' + out.extend(self.start_extension(s)) + + out.extend(self.start_sequence()) + + out.extend(self.make_form_inputs(form)) + + out.extend(self.end_sequence()) + if 'subform' not in form['ext']: + out.extend(self.end_extension()) + out.extend(self.end_complex_content()) + + out.extend(self.end_complex_type()) + + out.append(' ') + out.append(' ') + out.append(' ') + return out + + + def schemaHeader(self): + + host = self.configData['host'] + + if not host.endswith('/'): + host = '{}/'.format(host) + + out = [] + + out.append('') + out.append('') + out.append(' ') + out.append(' ') + out.append(' ') + #out.extend(self.addAssessCompleted()) + + return out + + + def addAssessCompleted(self): + + out =[] + + out.append(' ') + out.append(' ') + out.append(' ') + out.append(' ') + out.append(' ') + out.append(' ') + out.append(' ') + out.append(' ') + out.append(' ') + out.append(' ') + out.append(' ') + out.append(' ') + out.append(' ') + out.append(' ') + out.append(' ') + out.append(' ') + out.append(' ') + return out + + + def formName(self,form): + + out = [] + if 'subform' not in form['ext']: + out.append(' '.format( form['ref'], self.schemaName,form['ref']) ) + return out + + def make_form_desc(self,formDesc): + + out = [] + #out.append(' ') + #out.append(' {}'.format(formDesc) ) + #out.append(' ') + return out + + + def make_form_inputs(self,form): + + out = [] + doneDataNames = [] + + for i in form['inputs']: + + + dataName = i['dataName'] + self.log.append(' DATANAME: {} - {}'.format(dataName, i['inputFormType'])) + if 'text' in i['schemaInputType']: + self.log.append('Skipping as header or paragraph - {}'.format(i['question'])) + elif len(i['children']) > 0: + # self.log.append(' create group {}, form type : {} , datatype : {}'.format(i['dataName'], i['inputFormType'],i['schemaInputType'])) + + self.log.append(' Group input : {}'.format(i['dataName'])) + self.log.append(' -- Group input : {}'.format(i['question'])) + out.extend(self.make_complex_inputs(dataName,i)) + + else: + self.log.append(' non - group input : {}'.format(i['question'])) + #### if restriction"""" + + if i['inputLen'] > 0: + out.extend(self.input_restrict(dataName,i)) + else: + out.extend(self.input_default(dataName,i)) + + return out + + + def make_complex_inputs(self,dataName,input): + # self.traverse(input) + out = [] + out.append(''.format(input['dataName'])) + out.extend(self.start_complex_type()) + out.extend(self.start_sequence()) + + out.extend(self.input_default(input['subName'], input)) + + for i in input['children']: + self.log.append(' -- Group input : {}'.format(i['question'])) + + if 'text' not in i['schemaInputType']: + if i['inputLen'] > 0: + out.extend(self.input_restrict(i['subName'],i)) + else: + out.extend(self.input_default(i['subName'], i)) + + out.extend(self.end_sequence()) + out.extend(self.end_complex_type()) + + out.append('') + + return out + + + def input_restrict(self,elName,data): + + out = [] + + out.append(' '.format(elName, data['required'] )) + out.extend(self.input_question(data['question']) ) + out.append(' ') + out.append(' '.format( data['schemaInputType']) ) + if 'tring' in data['schemaInputType']: + out.append(' '.format( data['inputLen']) ) + else: + out.append(' ') + out.append(' '.format( data['inputLen']) ) + out.append(' ') + out.append(' ') + out.append(' ') + + return out + + def input_default(self,elName,data): + + if data['inputFormType'] == 'ynuo' : + return self.input_ynuo(elName) + + out = [] + if 'text' not in data['schemaInputType']: + if 'subform' in data['inputFormType']: + out.append(' '.format(elName, self.schemaName, data['schemaInputType'], data['required'])) + else: + out.append(' '.format(elName,data['schemaInputType'], data['required'] )) + out.extend(self.input_question(data['question']) ) + out.append(' ') + return out + + def input_ynuo(self,elName): + out = [] + out.append(' ') + out.append(' ') + out.append(' ') + out.append(' ') + out.append(' ') + out.append(' ') + out.append(' ') + out.append(' ') + out.append(' ') + out.append(' ') + return out + #return [' \n'.format(elName,self.schemaName)] + + def input_question(self,q): + + out = [] + + #out.append(' ') + #out.append(' {}'.format(q) ) + #out.append(' ') + return out + + + def end_schema(self,): + + return [''] + + def start_complex_type(self): + + return [''] + + def start_complex_type_named(self,name): + + return [''.format( name)] + + def end_complex_type(self): + + + return [''] + + def start_complex_content(self): + + return [''] + + def end_complex_content(self): + + return [''] + + def start_extension(self,e): + + return [''.format(e)] + + def end_extension(self): + + return [''] + + def start_sequence(self): + + return [''] + + def end_sequence(self): + + return [''] \ No newline at end of file diff --git a/builder_templates/174/ProjectSelector.vm b/builder_templates/174/ProjectSelector.vm new file mode 100644 index 0000000..04f4964 --- /dev/null +++ b/builder_templates/174/ProjectSelector.vm @@ -0,0 +1,96 @@ +##REQUIRES $item=org.nrg.xft.XFTItem $user=org.nrg.xdat.security.XDATUser + +#set($user=$data.getSession().getAttribute("user")) +#set($create_projects= $user.getAllowedValues("$item.getXSIType()","$item.getXSIType()/project","create")) + #set($projectMap = $user.getCachedItemValuesHash("xnat:projectData","read",false,"xnat:projectData/ID","xnat:projectData/secondary_ID")) +#if($project) + #if($item.getProperty("project")) + + #else + $item.setProperty("$item.getXSIType()/project",$project) + #end +#else + #set($project=$item.getProperty("project")) +#end +
    {}') + + if 'ynuo' in iType: + out.append(' #fmRadioYesNoUnableOtherTogglePanel("{}/completed" $item "{}" $vr)'.format(el, panel_name)) + out.append(' #set (${} = $!item.getProperty("{}/completed"))'.format(tog_name, el)) + + out.append(' '.format(panel_name,tog_name) ) + out.append(' #getUnableCategories()') + out.append(' '.format(el) ) + out.append('
    Reason for not completing:#renderfmScalarPropertySelect("{}/reasonNotAble" $item $unableCategories $vr)
    ') + elif 'ny' in iType: + out.append(' #fmRadioNoYesToggle("{}" $item 0 "{}" $vr)'.format(el, panel_name)) + out.append(' #set (${} = $!item.getProperty("{}"))'.format(tog_name, el)) + elif 'multipanel' in iType: + out.append(' #fmRadioYesNoBothToggle("{}" $item 0 "{}Y" "{}N" $vr)'.format(el, panel_name, panel_name)) + out.append(' #set (${} = $!item.getProperty("{}"))'.format(tog_name, el)) + else: + out.append(' #fmRadioYesNoToggle("{}" $item 0 "{}" $vr)'.format(el, panel_name)) + out.append(' #set (${} = $!item.getProperty("{}"))'.format(tog_name, el)) + + + childRows = [] + childRowsY = [] + childRowsN = [] + #if i have children, all for answer yes at the moment + if len(input['children']) > 0: + if 'multipanel' in iType: + ii = 0 + for c in input['children']: + el = '{}:{}/{}/{}'.format(c['schema'], c['formRef'], input['dataName'], c['subName']) + if ii < yes_qs: + htmlInputEditStandard(c, el, childRowsY) + else: + htmlInputEditStandard(c, el, childRowsN) + ii += 1 + + else: + for c in input['children']: + el='{}:{}/{}/{}'.format(c['schema'],c['formRef'],input['dataName'],c['subName']) + htmlInputEditStandard(c,el, childRows) + #print('adding child rows to toggle panel:{} {} {} {}'.format(el, c['subName'],c['schema'],c['formRef']) ) + if len(childRows) > 0 and 'ynuo' in iType: + out.append(' '.format(panel_name,tog_name) ) + out.extend(childRows) + out.append('
    ') + elif len(childRows) > 0 and 'yn' in iType: + out.append(' '.format(panel_name,tog_name) ) + out.extend(childRows) + out.append('
    ') + if len(childRows) > 0 and 'ny' in iType: + out.append(' '.format(panel_name,tog_name) ) + out.extend(childRows) + out.append('
    ') + if len(childRowsY) > 0 and 'multip' in iType: + out.append(' '.format(panel_name,tog_name) ) + out.extend(childRowsY) + out.append('
    ') + out.append(' '.format(panel_name,tog_name) ) + out.extend(childRowsN) + out.append('
    ') + out.append('

    {}') + + if iType == 'radio': + out.append(reportTypeRadio(input, el)) + + elif iType == 'subform': + out.extend(reportTypeSubform(input, el)) + + elif iType == 'dropdown' and dType == 'int': + + out.extend(reportTypeDropdown(input,el)) + + elif iType == 'tickbox': + out.append(reportTypeTickBox(el)) + + elif iType == 'date': + out.append(reportTypeDateBox(el)) + + elif iType == 'datetime': + out.append(reportTypeDateTimeBox(el)) + + elif iType == 'ynredcap': + #just shows boolean + out.append(reportTypeTickBox(el)) + + else: + out.append(reportTypeText(el)) + + out.append('

    {}') + head.append('
    {}') + head.append('
    {}
    {}') + + if 'ynuo' in iType: + out.append(' #showNoYesUnableOther("{}/completed" $item "{}" $vr)'.format(el, panel_name)) + out.append(' #set (${} = $!item.getProperty("{}/completed"))'.format(tog_name, el)) + + out.append(' '.format(panel_name,tog_name) ) + out.append(' '.format(el)) + out.append('
    Reason for not completing: #showReasonUnable("{}/reasonNotAble" $item)
    ') + else: + out.append(' #showYesNo("{}" $item "{}")'.format(el, panel_name)) + out.append(' #set (${} = $!item.getProperty("{}"))'.format(tog_name, el)) + + childRows = [] + childRowsY = [] + childRowsN = [] + #if i have children, all for answer yes at the moment + if len(input['children']) > 0: + if 'multipanel' in iType: + ii = 0 + for c in input['children']: + el = '{}:{}/{}/{}'.format(c['schema'], c['formRef'], input['dataName'], c['subName']) + if ii < yes_qs: + htmlInputReportStandard(c,el, childRowsY) + else: + htmlInputReportStandard(c,el, childRowsN) + ii += 1 + + else: + for c in input['children']: + el='{}:{}/{}/{}'.format(c['schema'],c['formRef'],input['dataName'],c['subName']) + htmlInputReportStandard(c,el, childRows) + print('adding child rows to report panel:{} {} {} {}'.format(el, c['subName'],c['schema'],c['formRef']) ) + #childRows.extend(htmlInputReport(c,c['schema'],c['formRef'])) + print('itype: {} childRowsN {}'.format(iType, childRowsN)) + if len(childRows) > 0 and 'yn' in iType: + out.append(' '.format(panel_name,tog_name) ) + out.extend(childRows) + out.append('
    ') + if len(childRows) > 0 and 'ny' in iType: + out.append(' '.format(panel_name,tog_name) ) + out.extend(childRows) + out.append('
    ') + if len(childRowsY) > 0 and 'multip' in iType: + out.append(' '.format(panel_name,tog_name) ) + out.extend(childRowsY) + out.append('
    ') + out.append(' '.format(panel_name,tog_name) ) + out.extend(childRowsN) + out.append('
    ') + + out.append('
    + +#if($preventLabel) + +#else + +#if($visit) + $item.setProperty("$item.getXSIType()/visit",$visit) +#else + #set($visit=$item.getProperty("visit")) +#end + +#if($visit) + + + + + +#end +#end +
    +Primary $displayManager.getSingularDisplayNameForProject(): +#if($item.getProperty("project")) + +#if($projectMap.get($item.getProperty("project"))) +$projectMap.get($item.getProperty("project")) +#else +$item.getProperty("project") +#end +#else +#if($create_projects.size()>0) + +#else +ERROR: No $displayManager.getPluralDisplayNameForProject().toLowerCase() exist. Please create a $displayManager.getSingularDisplayNameForProject().toLowerCase() before attempting to insert this item. +#end +#end +
    Visit:
    + + + \ No newline at end of file diff --git a/builder_templates/174/SubjectFinder.vm b/builder_templates/174/SubjectFinder.vm new file mode 100644 index 0000000..a7f18af --- /dev/null +++ b/builder_templates/174/SubjectFinder.vm @@ -0,0 +1,72 @@ + + + + + + +#if($part) + +#else + #set($part=$om.getSubjectData()) +#end + + +
    + + diff --git a/builder_templates/174/action_box_template.vm b/builder_templates/174/action_box_template.vm new file mode 100644 index 0000000..8385572 --- /dev/null +++ b/builder_templates/174/action_box_template.vm @@ -0,0 +1,15 @@ +
  • + Add [SCHEMA] assessment +
    +
    +
      + + + + +#* @vtlvariable name="items" type="java.util.List" *# + #set($value = "") + #set($value = $!item.getProperty($property)) + #if($value && !$value.equals("")) + $items[$value] + #else + (No value set) + #end + #set($value = "") + +#end + +#macro(showScalarProperty $property $item $items ) + +#* @vtlvariable name="items" type="java.util.List" *# + #set($value = "") + #set($valInt = 0) + #set($value = $!item.getProperty($property)) + #if($value && !$value.equals("")) + $items[$valInt.parseInt($value)] + #else + (No value set) + #end + #set($value = "") + +#end + + +#macro(showNoYesUnableOther $property $item ) + #set ($value = "") + #set($value = $!item.getProperty($property)) + #if("" == $value) + (No value selected) + #elseif($value) + #if("$value" == "0")  No #end + #if("$value" == "1")  Yes #end + #if("$value" == "2")  Unable for Health Reasons #end + #if("$value" == "3")  Unable, other #end + #else + (Not specified) + #end + +#end + + +#macro(showYesNo $property $item ) + #set ($value = "") + #set($value = $!item.getProperty($property)) + #if("" == $value) + (No value selected) + #elseif($value) + #if("$value" == "0")  No #end + #if("$value" == "1")  Yes #end + #else + (Not specified) + #end + +#end + + + + +#macro(showReasonUnable $property $item ) + #getUnableCategories() + #set ($value = "") + #set($value = $!item.getProperty($property)) + #if("" == $value) + (No value selected) + #elseif($value) + #showScalarIntProperty($property $item $unableCategories ) + #else + (No reason given) + #end +#end + +#macro(showfmBoolean $property $item) + + #set ($value = "") + + #set($value = $!item.getProperty($property)) + #if("" == $value) + (No value selected) + + #elseif ("$value" == "0") + No + #else + Yes + #end + +#end + +#macro (fmBooleanRadioYesNo $name $item $defaultValue $vr) + #if ($vr) + #if($vr.getField($name)) + + #end + #end + #if ($item.getBooleanProperty($name,$defaultValue)) +  No +  Yes + + #else +  No +  Yes + + #end + #end + +#macro (fmRadioYesNoUnableOtherTogglePanel $name $item $panel $vr) + + #set ($value = "") + #set($value = $!item.getProperty($name)) + +  No +  Yes +  Unable for Health Reasons +  Unable, other (Specify) + +#end + + +#macro(fmRadioYesNo $name $item $defaultValue $vr) + #if($item.getBooleanProperty($name,$defaultValue)) +  No +  Yes + #else +  No +  Yes + #end +#end + + + +#macro(fmRadioYesNoToggle $name $item $defaultValue $panel $vr) + #set ($value = "") + #set($value = $!item.getProperty($name)) +  No +  Yes +#end + +#macro(fmRadioNoYesToggle $name $item $defaultValue $panel $vr) + #set ($value = "") + #set($value = $!item.getProperty($name)) +  No +  Yes +#end + +#macro(fmRadioYesNoBothToggle $name $item $defaultValue $panelY $panelN $vr) + #set ($value = "") + #set($value = $!item.getProperty($name)) +  No +  Yes +#end + +#macro(fmRedcapBoolToggle $name $default $panel $vals $vr) + #set ($value = "") + #set($value = $!item.getProperty($name)) + #if ("" == $value && "" != $default) + #set ($value = $default) + #end + + + #set ($pInt = 1) + #set ($p = "$panel$pInt") + #set ($onclickY = "") + #set ($onclickN = "") + #foreach( $val in $vals.split(",") ) + #if("$val" == "Y") + #set ($onclickY = "${onclickY}togglePanel('${p}', true);") + #set ($onclickN = "${onclickN}togglePanel('${p}', false);") + #else + #set ($onclickY = "${onclickY}togglePanel('${p}', false);") + #set ($onclickN = "${onclickN}togglePanel('${p}', true);") + #end + #set ($pInt = $pInt + 1) + #set ($p = "$panel$pInt") + #end +  No +  Yes +#end + + + +#macro(fmGenericStringToggle $name $item $items $default $panel $vals $vr) + #set ($value = "") + #set($value = $!item.getProperty($name)) + #if ("" == $value && "" != $default) + #set ($value = $default) + #end + + #foreach($listitem in $items) + #set ($pInt = 1) + #set ($p = "$panel$pInt") + #set ($onclick = "") + #foreach( $val in $vals.split(",") ) + #if("$listitem" == "$val") + #set ($onclick = "${onclick}togglePanel('${p}', true);") + #else + #set ($onclick = "${onclick}togglePanel('${p}', false);") + #end + #set ($pInt = $pInt + 1) + #set ($p = "$panel$pInt") + #end +  $listitem + #end + +#end + +#macro(renderfmScalarPropertySelect $property $item $items $vr) + #set ($value = "") + #set($value = $!item.getProperty($property)) + +#end + +#macro(renderfmIntegerDropdown $property $item $items $vr) + #set ($value = "") + #set($value = $!item.getProperty($property)) + +#end + +#macro(renderfmStringDropdown $property $item $items $default $vr) + #set ($value = "") + #set($value = $!item.getProperty($property)) + #if ("" == $value && "" != $default) + #set ($value = $default) + + + #else + + #end +#end + + +#macro(renderfmStringRadio $name $item $items $vr) + #set($value = "") + #set($value = $!item.getProperty($name)) + + #foreach($listitem in $items) +  $listitem + #end + +#end +#macro(renderfmStringMultiRadio $name $item $items $vr) + #set($value = "") + #set($value = $!item.getProperty($name)) + + #foreach($listitem in $items) +  $listitem + #end + +#end + + +#macro (fmDateBox $property $item $vr) + #set($date = "") + #set($rawDate = "") + + #set($rawDate = $!item.getDateProperty($property)) + #if("" == $rawDate) + #set($date = "") + #else + #set($date = $turbineUtils.formatDate($!item.getDateProperty($property), "yyyy-MM-dd")) + #end + + + +#end + + +#macro (fmDateTimev1Box $property $item $vr $num) + + + + + + + +#end + + +#macro (fmDateTimeLocalBox $property $item $vr) + + +#end + + + +#macro (fmDateTimeBox $property $item $vr $num) + + + #set ($value = "") + #set($value = $!item.getProperty($property)) + + #if ("" == $value) + #set ($saveddate = "01-01-2023") + #set ($savedtime = "12:00") + + #else + #set ($s = $value.split("T")) + #set ($df = $s.get(0).split("-")) + #set ($saveddate = "$df.get(2)-$df.get(1)-$df.get(0)") + #set ($savedtime = $s.get(1).substring(0,5)) + + + #end + + + + + + + + + + + +#end + + +#macro (fmTimeBox $property $item $vr $num) + + + + #set($value = "") + #set($value = $!item.getProperty($property)) + + + + + + + + + +#end + + + +#macro (fmBooleanCheckbox $name $item $vr $default) + #set ($value = "") + +#if ($vr) + #if($vr.getField($name)) + + #end +#end + #set($value = $!item.getBooleanProperty($name)) + +#if ($value) + +#else + +#end + + ## The hidden box is necessary to submit a "false" value if the checkbox is unchecked + ## If the checkbox is checked, its true value will override the hidden's "false" value + +#end + + + + +#macro (fmCalcType $property $item $vr $type) + + +#end + +#macro (showDateTime $property $item ) + + #set($value = "") + #set($value = $!item.getProperty($property)) + #if ("" == $value) + No date set + #else + #set($s = $value.split("T")) + #set($df = $s.get(0).split("-")) + $df.get(2)-$df.get(1)-$df.get(0) $s.get(1) + #end + +#end + +#macro (showDate $property $item ) + + #set($date = "") + #set($rawDate = "") + + #set($rawDate = $!item.getDateProperty($property)) + #if("" == $rawDate) + + No date set + #else + #set($date = $turbineUtils.formatDate($!item.getDateProperty($property), "dd-MM-yyyy")) + $date + #end + +#end \ No newline at end of file diff --git a/builder_templates/174/plugin_template/entities/Template.java b/builder_templates/174/plugin_template/entities/Template.java new file mode 100644 index 0000000..a14cf83 --- /dev/null +++ b/builder_templates/174/plugin_template/entities/Template.java @@ -0,0 +1,30 @@ +/* + * xnat-template: org.nrg.xnat.plugins.template.entities.Template + * XNAT http://www.xnat.org + * Copyright (c) 2017, Washington University School of Medicine + * All Rights Reserved + * + * Released under the Simplified BSD. + */ + +package org.nrg.xnat.plugins.[HERE_SCHEMANAME_LOWER].entities; + +import org.nrg.framework.orm.hibernate.AbstractHibernateEntity; + +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +@Entity +@Table(uniqueConstraints = @UniqueConstraint(columnNames = "templateId")) +public class [HERE_SCHEMANAME] extends AbstractHibernateEntity { + public String getTemplateId() { + return _templateId; + } + + public void setTemplateId(final String templateId) { + _templateId = templateId; + } + + private String _templateId; +} diff --git a/builder_templates/174/plugin_template/plugin/XnatTemplatePlugin.java b/builder_templates/174/plugin_template/plugin/XnatTemplatePlugin.java new file mode 100644 index 0000000..0ef5d8e --- /dev/null +++ b/builder_templates/174/plugin_template/plugin/XnatTemplatePlugin.java @@ -0,0 +1,49 @@ +/* + * xnat-template: org.nrg.xnat.plugins.template.plugin.XnatTemplatePlugin + * XNAT http://www.xnat.org + * Copyright (c) 2017, Washington University School of Medicine + * All Rights Reserved + * + * Released under the Simplified BSD. + */ + +package org.nrg.xnat.plugins.[HERE_SCHEMANAME_LOWER].plugin; + +import org.nrg.dcm.id.CompositeDicomObjectIdentifier; +import org.nrg.dcm.id.FixedProjectSubjectDicomObjectIdentifier; +import org.nrg.framework.annotations.XnatDataModel; +import org.nrg.framework.annotations.XnatPlugin; +[HERE_IMPORT_BEANS] +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.jdbc.core.JdbcTemplate; + +@XnatPlugin(value = "[HERE_SCHEMANAME_LOWER]Plugin", + name = "XNAT 1.7 [HERE_PLUGIN_NAME] Plugin", + entityPackages = "org.nrg.xnat.plugins.[HERE_SCHEMANAME_LOWER].entities", + dataModels = { + [HERE_XNAT_DATA_MODELS] + }) +@ComponentScan({"org.nrg.xnat.plugins.[HERE_SCHEMANAME_LOWER].preferences", + "org.nrg.xnat.plugins.[HERE_SCHEMANAME_LOWER].repositories", + "org.nrg.xnat.plugins.[HERE_SCHEMANAME_LOWER].rest", + "org.nrg.xnat.plugins.[HERE_SCHEMANAME_LOWER].services.impl"}) +public class Xnat[HERE_SCHEMANAME]Plugin { + public Xnat[HERE_SCHEMANAME]Plugin() { + _log.info("Creating the Xnat[HERE_SCHEMANAME]Plugin configuration class"); + } + + @Bean + public CompositeDicomObjectIdentifier projectXnat02Identifier(final JdbcTemplate template) { + return new FixedProjectSubjectDicomObjectIdentifier(template, "XNAT_02", "XNAT_02_01"); + } + + @Bean + public String [HERE_SCHEMANAME_LOWER]PluginMessage() { + return "This comes from deep within the [HERE_SCHEMANAME_LOWER] plugin."; + } + + private static final Logger _log = LoggerFactory.getLogger(Xnat[HERE_SCHEMANAME]Plugin.class); +} diff --git a/builder_templates/174/plugin_template/preferences/TemplatePreferencesBean.java b/builder_templates/174/plugin_template/preferences/TemplatePreferencesBean.java new file mode 100644 index 0000000..5bed3b0 --- /dev/null +++ b/builder_templates/174/plugin_template/preferences/TemplatePreferencesBean.java @@ -0,0 +1,54 @@ +/* + * xnat-template: org.nrg.xnat.plugins.template.preferences.TemplatePreferencesBean + * XNAT http://www.xnat.org + * Copyright (c) 2017, Washington University School of Medicine + * All Rights Reserved + * + * Released under the Simplified BSD. + */ + +package org.nrg.xnat.plugins.[HERE_SCHEMANAME_LOWER].preferences; + +import org.nrg.framework.configuration.ConfigPaths; +import org.nrg.prefs.annotations.NrgPreference; +import org.nrg.prefs.annotations.NrgPreferenceBean; +import org.nrg.prefs.beans.AbstractPreferenceBean; +import org.nrg.prefs.exceptions.InvalidPreferenceName; +import org.nrg.prefs.services.NrgPreferenceService; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; + +@NrgPreferenceBean(toolId = "template", toolName = "XNAT 1.7 Template Plugin") +public class [HERE_SCHEMANAME]PreferencesBean extends AbstractPreferenceBean { + @Autowired + public [HERE_SCHEMANAME]PreferencesBean(final NrgPreferenceService preferenceService, final ConfigPaths configFolderPaths) { + super(preferenceService, configFolderPaths); + } + + @NrgPreference(defaultValue = "['Standard']") + public List getTemplateNames() { + return getListValue("templateNames"); + } + + public void setTemplateNames(final List templateNames) { + try { + setListValue("templateNames", templateNames); + } catch (InvalidPreferenceName invalidPreferenceName) { + // + } + } + + @NrgPreference(defaultValue = "['standard']") + public List getTemplateTypes() { + return getListValue("templateTypes"); + } + + public void setTemplateTypes(final List templateTypes) { + try { + setListValue("templateTypes", templateTypes); + } catch (InvalidPreferenceName invalidPreferenceName) { + // + } + } +} diff --git a/builder_templates/174/plugin_template/repositories/TemplateRepository.java b/builder_templates/174/plugin_template/repositories/TemplateRepository.java new file mode 100644 index 0000000..11008b4 --- /dev/null +++ b/builder_templates/174/plugin_template/repositories/TemplateRepository.java @@ -0,0 +1,18 @@ +/* + * xnat-template: org.nrg.xnat.plugins.template.repositories.TemplateRepository + * XNAT http://www.xnat.org + * Copyright (c) 2017, Washington University School of Medicine + * All Rights Reserved + * + * Released under the Simplified BSD. + */ + +package org.nrg.xnat.plugins.[HERE_SCHEMANAME_LOWER].repositories; + +import org.nrg.framework.orm.hibernate.AbstractHibernateDAO; +import org.nrg.xnat.plugins.[HERE_SCHEMANAME_LOWER].entities.[HERE_SCHEMANAME]; +import org.springframework.stereotype.Repository; + +@Repository +public class [HERE_SCHEMANAME]Repository extends AbstractHibernateDAO<[HERE_SCHEMANAME]> { +} diff --git a/builder_templates/174/plugin_template/rest/TemplateApi.java b/builder_templates/174/plugin_template/rest/TemplateApi.java new file mode 100644 index 0000000..2e0c2ff --- /dev/null +++ b/builder_templates/174/plugin_template/rest/TemplateApi.java @@ -0,0 +1,102 @@ +/* + * xnat-template: org.nrg.xnat.plugins.template.rest.TemplateApi + * XNAT http://www.xnat.org + * Copyright (c) 2017, Washington University School of Medicine + * All Rights Reserved + * + * Released under the Simplified BSD. + */ + +package org.nrg.xnat.plugins.[HERE_SCHEMANAME_LOWER].rest; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import org.nrg.framework.annotations.XapiRestController; +import org.nrg.xapi.rest.AbstractXapiRestController; +import org.nrg.xapi.rest.XapiRequestMapping; +import org.nrg.xdat.security.services.RoleHolder; +import org.nrg.xdat.security.services.UserManagementServiceI; +import org.nrg.xnat.plugins.[HERE_SCHEMANAME_LOWER].entities.[HERE_SCHEMANAME]; +import org.nrg.xnat.plugins.[HERE_SCHEMANAME_LOWER].services.[HERE_SCHEMANAME]Service; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.util.List; + +@Api(description = "XNAT 1.7 [HERE_SCHEMANAME] Plugin API") +@XapiRestController +@RequestMapping(value = "/[HERE_SCHEMANAME_LOWER]/entities") +public class [HERE_SCHEMANAME]Api extends AbstractXapiRestController { + @Autowired + protected [HERE_SCHEMANAME]Api(final UserManagementServiceI userManagementService, final RoleHolder roleHolder, final [HERE_SCHEMANAME]Service templateService) { + super(userManagementService, roleHolder); + _templateService = templateService; + } + + @ApiOperation(value = "Returns a list of all templates.", response = [HERE_SCHEMANAME].class, responseContainer = "List") + @ApiResponses({@ApiResponse(code = 200, message = "[HERE_SCHEMANAME]s successfully retrieved."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 500, message = "Unexpected error")}) + @XapiRequestMapping(produces = {MediaType.APPLICATION_JSON_VALUE}, method = RequestMethod.GET) + public ResponseEntity> getEntities() { + return new ResponseEntity<>(_templateService.getAll(), HttpStatus.OK); + } + + @ApiOperation(value = "Creates a new template.", response = [HERE_SCHEMANAME].class) + @ApiResponses({@ApiResponse(code = 200, message = "[HERE_SCHEMANAME] successfully created."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 500, message = "Unexpected error")}) + @XapiRequestMapping(produces = {MediaType.APPLICATION_JSON_VALUE}, method = RequestMethod.POST) + public ResponseEntity<[HERE_SCHEMANAME]> createEntity(@RequestBody final [HERE_SCHEMANAME] entity) { + final [HERE_SCHEMANAME] created = _templateService.create(entity); + return new ResponseEntity<>(created, HttpStatus.OK); + } + + @ApiOperation(value = "Retrieves the indicated template.", + notes = "Based on the template ID, not the primary key ID.", + response = [HERE_SCHEMANAME].class) + @ApiResponses({@ApiResponse(code = 200, message = "[HERE_SCHEMANAME] successfully retrieved."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 500, message = "Unexpected error")}) + @XapiRequestMapping(value = "{id}", produces = {MediaType.APPLICATION_JSON_VALUE}, method = RequestMethod.GET) + public ResponseEntity<[HERE_SCHEMANAME]> getEntity(@PathVariable final String id) { + return new ResponseEntity<>(_templateService.findByTemplateId(id), HttpStatus.OK); + } + + @ApiOperation(value = "Updates the indicated template.", + notes = "Based on primary key ID, not subject or record ID.", + response = Void.class) + @ApiResponses({@ApiResponse(code = 200, message = "[HERE_SCHEMANAME] successfully updated."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 500, message = "Unexpected error")}) + @XapiRequestMapping(value = "{id}", produces = {MediaType.APPLICATION_JSON_VALUE}, method = RequestMethod.PUT) + public ResponseEntity updateEntity(@PathVariable final Long id, @RequestBody final [HERE_SCHEMANAME] entity) { + final [HERE_SCHEMANAME] existing = _templateService.retrieve(id); + existing.setTemplateId(entity.getTemplateId()); + _templateService.update(existing); + return new ResponseEntity<>(HttpStatus.OK); + } + + @ApiOperation(value = "Deletes the indicated template.", + notes = "Based on primary key ID, not subject or record ID.", + response = Void.class) + @ApiResponses({@ApiResponse(code = 200, message = "[HERE_SCHEMANAME] successfully deleted."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 500, message = "Unexpected error")}) + @XapiRequestMapping(value = "{id}", produces = {MediaType.APPLICATION_JSON_VALUE}, method = RequestMethod.DELETE) + public ResponseEntity deleteEntity(@PathVariable final Long id) { + final [HERE_SCHEMANAME] existing = _templateService.retrieve(id); + _templateService.delete(existing); + return new ResponseEntity<>(HttpStatus.OK); + } + + private final [HERE_SCHEMANAME]Service _templateService; +} diff --git a/builder_templates/174/plugin_template/rest/TemplatePrefsApi.java b/builder_templates/174/plugin_template/rest/TemplatePrefsApi.java new file mode 100644 index 0000000..f373c37 --- /dev/null +++ b/builder_templates/174/plugin_template/rest/TemplatePrefsApi.java @@ -0,0 +1,96 @@ +/* + * xnat-template: org.nrg.xnat.plugins.template.rest.TemplatePrefsApi + * XNAT http://www.xnat.org + * Copyright (c) 2017, Washington University School of Medicine + * All Rights Reserved + * + * Released under the Simplified BSD. + */ + +package org.nrg.xnat.plugins.[HERE_SCHEMANAME_LOWER].rest; + +import io.swagger.annotations.*; +import org.nrg.framework.annotations.XapiRestController; +import org.nrg.prefs.exceptions.InvalidPreferenceName; +import org.nrg.xapi.rest.AbstractXapiRestController; +import org.nrg.xapi.rest.XapiRequestMapping; +import org.nrg.xdat.security.services.RoleHolder; +import org.nrg.xdat.security.services.UserManagementServiceI; +import org.nrg.xnat.plugins.[HERE_SCHEMANAME_LOWER].preferences.[HERE_SCHEMANAME]PreferencesBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.util.Map; + +@Api(description = "XNAT 1.7 [HERE_SCHEMANAME] Plugin API") +@XapiRestController +@RequestMapping(value = "/[HERE_SCHEMANAME_LOWER]/prefs") +public class [HERE_SCHEMANAME]PrefsApi extends AbstractXapiRestController { + @Autowired + public [HERE_SCHEMANAME]PrefsApi(final UserManagementServiceI userManagementService, final RoleHolder roleHolder, final [HERE_SCHEMANAME]PreferencesBean templatePrefs) { + super(userManagementService, roleHolder); + _templatePrefs = templatePrefs; + } + + @ApiOperation(value = "Returns the full map of template preferences.", response = String.class, responseContainer = "Map") + @ApiResponses({@ApiResponse(code = 200, message = "Site configuration properties successfully retrieved."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to set site configuration properties."), + @ApiResponse(code = 500, message = "Unexpected error")}) + @XapiRequestMapping(produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET) + public ResponseEntity> getTemplatePreferences() { + final HttpStatus status = isPermitted(); + if (status != null) { + return new ResponseEntity<>(status); + } + return new ResponseEntity<>(_templatePrefs.getPreferenceMap(), HttpStatus.OK); + } + + @ApiOperation(value = "Returns the value of the specified template preference.", response = String.class) + @ApiResponses({@ApiResponse(code = 200, message = "Template preference value successfully retrieved."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to access template preferences."), + @ApiResponse(code = 500, message = "Unexpected error")}) + @XapiRequestMapping(value = "{preference}", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET) + public ResponseEntity getPreferenceValue(@ApiParam(value = "The template preference to retrieve.", required = true) @PathVariable final String preference) { + final HttpStatus status = isPermitted(); + if (status != null) { + return new ResponseEntity<>(status); + } + if (!_templatePrefs.getPreferenceKeys().contains(preference)) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + return new ResponseEntity<>(_templatePrefs.getValue(preference), HttpStatus.OK); + } + + @ApiOperation(value = "Updates the value of the specified template preference.", response = Void.class) + @ApiResponses({@ApiResponse(code = 200, message = "Template preference value successfully retrieved."), + @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), + @ApiResponse(code = 403, message = "Not authorized to access template preferences."), + @ApiResponse(code = 500, message = "Unexpected error")}) + @XapiRequestMapping(value = "{preference}", consumes = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.PUT) + public ResponseEntity setPreferenceValue(@ApiParam(value = "The template preference to set.", required = true) @PathVariable final String preference, + @ApiParam(value = "The template preference to set.", required = true) @RequestBody final String value) { + final HttpStatus status = isPermitted(); + if (status != null) { + return new ResponseEntity<>(status); + } + if (!_templatePrefs.getPreferenceKeys().contains(preference)) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + try { + _templatePrefs.set(value, preference); + } catch (InvalidPreferenceName invalidPreferenceName) { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + return new ResponseEntity<>(HttpStatus.OK); + } + + private final [HERE_SCHEMANAME]PreferencesBean _templatePrefs; +} diff --git a/builder_templates/174/plugin_template/services/TemplateService.java b/builder_templates/174/plugin_template/services/TemplateService.java new file mode 100644 index 0000000..9fe7b53 --- /dev/null +++ b/builder_templates/174/plugin_template/services/TemplateService.java @@ -0,0 +1,24 @@ +/* + * xnat-template: org.nrg.xnat.plugins.template.services.TemplateService + * XNAT http://www.xnat.org + * Copyright (c) 2017, Washington University School of Medicine + * All Rights Reserved + * + * Released under the Simplified BSD. + */ + +package org.nrg.xnat.plugins.[HERE_SCHEMANAME_LOWER].services; + +import org.nrg.framework.orm.hibernate.BaseHibernateService; +import org.nrg.xnat.plugins.[HERE_SCHEMANAME_LOWER].entities.[HERE_SCHEMANAME]; + +public interface [HERE_SCHEMANAME]Service extends BaseHibernateService<[HERE_SCHEMANAME]> { + /** + * Finds the template with the indicated {@link [HERE_SCHEMANAME]#getTemplateId() template ID}. + * + * @param templateId The template ID. + * + * @return The template with the indicated ID, null if not found. + */ + [HERE_SCHEMANAME] findByTemplateId(final String templateId); +} diff --git a/builder_templates/174/plugin_template/services/impl/HibernateTemplateService.java b/builder_templates/174/plugin_template/services/impl/HibernateTemplateService.java new file mode 100644 index 0000000..337011f --- /dev/null +++ b/builder_templates/174/plugin_template/services/impl/HibernateTemplateService.java @@ -0,0 +1,32 @@ +/* + * xnat-template: org.nrg.xnat.plugins.template.services.impl.HibernateTemplateService + * XNAT http://www.xnat.org + * Copyright (c) 2017, Washington University School of Medicine + * All Rights Reserved + * + * Released under the Simplified BSD. + */ + +package org.nrg.xnat.plugins.[HERE_SCHEMANAME_LOWER].services.impl; + +import org.nrg.framework.orm.hibernate.AbstractHibernateEntityService; +import org.nrg.xnat.plugins.[HERE_SCHEMANAME_LOWER].entities.[HERE_SCHEMANAME]; +import org.nrg.xnat.plugins.[HERE_SCHEMANAME_LOWER].repositories.[HERE_SCHEMANAME]Repository; +import org.nrg.xnat.plugins.[HERE_SCHEMANAME_LOWER].services.[HERE_SCHEMANAME]Service; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * Manages {@link [HERE_SCHEMANAME]} data objects in Hibernate. + */ +@Service +public class Hibernate[HERE_SCHEMANAME]Service extends AbstractHibernateEntityService<[HERE_SCHEMANAME], [HERE_SCHEMANAME]Repository> implements [HERE_SCHEMANAME]Service { + /** + * {@inheritDoc} + */ + @Transactional + @Override + public [HERE_SCHEMANAME] findByTemplateId(final String templateId) { + return getDao().findByUniqueProperty("templateId", templateId); + } +} diff --git a/builder_templates/174/scripts/formmaker.js b/builder_templates/174/scripts/formmaker.js new file mode 100644 index 0000000..329c9d6 --- /dev/null +++ b/builder_templates/174/scripts/formmaker.js @@ -0,0 +1,14 @@ + function calcDateDiff(propertyOne, propertyTqo, property) { + var start = document.getElementById(propertyOne).value; + var end = document.getElementById(propertyTwo).value + // end - start returns difference in milliseconds + var diff = new Date(end - start); + // get days + document.getElementById(property).value = diff/1000/60/60/24; + } + + function updateTime(property, time) { + tm = document.getElementById(time).value; + + document.getElementById(property).value = tm; + } \ No newline at end of file diff --git a/builder_templates/174/scripts/jquery-timepicker/AUTHORS b/builder_templates/174/scripts/jquery-timepicker/AUTHORS new file mode 100644 index 0000000..4bf9092 --- /dev/null +++ b/builder_templates/174/scripts/jquery-timepicker/AUTHORS @@ -0,0 +1,17 @@ +/** + * jQuery Timepicker - v1.3.5 - 2016-07-10 + * http://timepicker.co + * + * Enhances standard form input fields helping users to select (or type) times. + * + * Copyright (c) 2016 Willington Vega; Licensed MIT, GPL + */ + +# Authors ordered by first contribution. + +Willington Vega +Brian Link +Ian Enders +Jo Liss https://github.com/joliss +Billy Bednar https://github.com/billybednar +Conrad Frame https://github.com/sponrad diff --git a/builder_templates/174/scripts/jquery-timepicker/CHANGELOG b/builder_templates/174/scripts/jquery-timepicker/CHANGELOG new file mode 100644 index 0000000..288056b --- /dev/null +++ b/builder_templates/174/scripts/jquery-timepicker/CHANGELOG @@ -0,0 +1,88 @@ +/** + * jQuery Timepicker - v1.3.5 - 2016-07-10 + * http://timepicker.co + * + * Enhances standard form input fields helping users to select (or type) times. + * + * Copyright (c) 2016 Willington Vega; Licensed MIT, GPL + */ + +* Sat Jul 9 2016 - Version 1.3.5 + +- Fix stray comma in bower.json (#81). + +* Sun Jun 26 2016 - Version 1.3.4 + +- Allow jquery-timepicker to work with Browserify (#75). +- Add Bower support thanks to @madalinignisca (package name: jquery-timepicker-wvega) (#74). + +* Fri May 20 2016 - Version 1.3.3 +- Prevent dropdown from closing in IE when user clicks scrollbar (#56). +- Prevent long-click from closing the timepicker (#50). + +* Sat Sep 13 2014 - Version 1.3.2 +- Add defaultTime option (#41). +- Fix position bug (#43). + +* Sat Nov 09 2013 - Version 1.3.1 +- Fix minor errors in jQuery Package Manifest. + +* Sat Nov 09 2013 - Version 1.3.0 +- Drop support for jQuery 1.4.2 or below. Plugin requires jQuery 1.6.0 or newer. +- Fix: Avoid showing dupliate time entries (#21) +- Fix: next, previous, open and close methods are now chainable (#23) +- Fix: getTime now takes into account values set using $.fn.val (#24) +- Fix: Parse values like 12:77 into a valid datetime entry (#27). +- Fix: Parse values like 6666 into a valid datetime entry (#33). +- Fix: Use default cursor in time selector dropdown (#34). +- Fix: Remove ReferenceError: event is not defined (#39). +- Added lowercase am/pm format. +- time-change event and change callbacks are triggered even if the new + time is null. +- Minor CSS fixes. + +* Sun Feb 26 2012 - Version 1.2.2 +- Fix too much recursion error in parseTime function (#20). +- Verifies argument passed to parseTime is string. +- Add support for jQuery 1.6 prop function. + +* Mon Nov 14 2011 - Version 1.2.1 +- Fix strings support for startTime, minTime and maxTime options. + +* Sun Nov 13 2011 - Version 1.2.0 +- Add support for calling API methods using the plugin function, + passing the method's name as first argument and options as + subsequent arguments. +- Add an option() method to allow changing options values after + initialization. +- Add dropdown option to control whether the dropdown is displayed or not. +- Add scrollbar option to control whether the scrollbars are displayed or not. +- Aceept strings as values for the minTime, maxTime and startTime options. + +* Tue Jul 26 2011 - Version 1.1.2 +- Released under MIT and GPL Version 2 licenses. +- Add zindex option to override input field's z-index (thanks to Ian Enders). + +* Tue Nov 23 2010 - Version 1.1.1 +- Fix Issue #11 (https://github.com/wvega/timepicker/issues/11) + +* Mon Nov 08 2010 - Version 1.1.0 +- Use input field's z-index. +- Do not use rounded corners by default. +- Use a single UL element for all timepicker in the same page. +- Interval greather than 60 minutes are now supported. +- Add time-change custom event. + +* Fri Oct 29 2010 - Version 1.0.3 +- Add dynamic option + +* Fri Oct 08 2010 - Version 1.0.2 +- Add support for jQuery 1.3 and superior (thanks to Brian Link) +- Fix bug with jQuery 1.4.3 + +* Thu Oct 07 2010 - Version 1.0.1 +- Fix selection issue in some versions of IE. + http://github.com/wvega/timepicker/issues/closed/#issue/3 + +* Wed Jul 17 2010 - Version 1.0.0 +- jQuery Timepicker official release diff --git a/builder_templates/174/scripts/jquery-timepicker/LICENSE-GPL b/builder_templates/174/scripts/jquery-timepicker/LICENSE-GPL new file mode 100644 index 0000000..b89f676 --- /dev/null +++ b/builder_templates/174/scripts/jquery-timepicker/LICENSE-GPL @@ -0,0 +1,287 @@ +/** + * jQuery Timepicker - v1.3.5 - 2016-07-10 + * http://timepicker.co + * + * Enhances standard form input fields helping users to select (or type) times. + * + * Copyright (c) 2016 Willington Vega; Licensed MIT, GPL + */ + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. diff --git a/builder_templates/174/scripts/jquery-timepicker/LICENSE-MIT b/builder_templates/174/scripts/jquery-timepicker/LICENSE-MIT new file mode 100644 index 0000000..7f36ce5 --- /dev/null +++ b/builder_templates/174/scripts/jquery-timepicker/LICENSE-MIT @@ -0,0 +1,36 @@ +/** + * jQuery Timepicker - v1.3.5 - 2016-07-10 + * http://timepicker.co + * + * Enhances standard form input fields helping users to select (or type) times. + * + * Copyright (c) 2016 Willington Vega; Licensed MIT, GPL + */ + +jQuery TimePicker plugin + +A jQuery plugin to enhance standard form input fields helping +users to select (or type) times. + +Copyright (c) 2016 Willington Vega + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/builder_templates/174/scripts/jquery-timepicker/README.md b/builder_templates/174/scripts/jquery-timepicker/README.md new file mode 100644 index 0000000..ad7464c --- /dev/null +++ b/builder_templates/174/scripts/jquery-timepicker/README.md @@ -0,0 +1,23 @@ +/** + * jQuery Timepicker - v1.3.5 - 2016-07-10 + * http://timepicker.co + * + * Enhances standard form input fields helping users to select (or type) times. + * + * Copyright (c) 2016 Willington Vega; Licensed MIT, GPL + */ + +# jQuery Timepicker [![Build Status](http://img.shields.io/travis/wvega/timepicker.svg)](https://travis-ci.org/wvega/timepicker) [![Gitter chat](https://badges.gitter.im/wvega/timepicker.png)](https://gitter.im/wvega/timepicker) + +A jQuery plugin to enhance standard form input fields helping users to select +(or type) times. + +Please visit http://timepicker.co for Documentation, Examples and Getting Started information. + +## Release History + +See [CHANGELOG](https://github.com/wvega/timepicker/blob/master/CHANGELOG). + +## License +Copyright (c) 2016 Willington Vega +Licensed under the MIT, GPL licenses. diff --git a/builder_templates/174/scripts/jquery-timepicker/jquery.timepicker.css b/builder_templates/174/scripts/jquery-timepicker/jquery.timepicker.css new file mode 100644 index 0000000..81c091c --- /dev/null +++ b/builder_templates/174/scripts/jquery-timepicker/jquery.timepicker.css @@ -0,0 +1,84 @@ +/** + * jQuery Timepicker - v1.3.5 - 2016-07-10 + * http://timepicker.co + * + * Enhances standard form input fields helping users to select (or type) times. + * + * Copyright (c) 2016 Willington Vega; Licensed MIT, GPL + */ + +.ui-timepicker-container { + position: absolute; + overflow: hidden; + box-sizing: border-box; +} + +.ui-timepicker { + box-sizing: content-box; + display: block; + height: 205px; + list-style: none outside none; + margin: 0; + padding: 0 1px; + text-align: center; +} + +.ui-timepicker-viewport { + box-sizing: content-box; + display: block; + height: 205px; + margin: 0; + padding: 0; + overflow: auto; + overflow-x: hidden; /* IE */ +} + +.ui-timepicker-standard { + /* overwrites .ui-widget */ + font-family: Verdana,Arial,sans-serif; + font-size: 1.1em; + /* overwrites .ui-widget-content */ + background-color: #FFF; + border: 1px solid #AAA; + color: #222; + /* overwrites .ui-menu */ + margin: 0; + padding: 2px; +} +.ui-timepicker-standard a { + border: 1px solid transparent; + color: #222; + display: block; + padding: 0.2em 0.4em; + text-decoration: none; +} +.ui-timepicker-standard .ui-state-hover { + /* overwrites .ui-state-hover */ + background-color: #DADADA; + border: 1px solid #999; + font-weight: normal; + color: #212121; +} +.ui-timepicker-standard .ui-menu-item { + /* overwrites .ui-menu and .ui-menu-item */ + /*clear: left; + float: left;*/ + margin: 0; + padding: 0; +} + +.ui-timepicker-corners, +.ui-timepicker-corners .ui-corner-all { + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; +} + +.ui-timepicker-hidden { + /* overwrites .ui-helper-hidden */ + display: none; +} + +.ui-timepicker-no-scrollbar .ui-timepicker { + border: none; +} diff --git a/builder_templates/174/scripts/jquery-timepicker/jquery.timepicker.js b/builder_templates/174/scripts/jquery-timepicker/jquery.timepicker.js new file mode 100644 index 0000000..68d264b --- /dev/null +++ b/builder_templates/174/scripts/jquery-timepicker/jquery.timepicker.js @@ -0,0 +1,864 @@ +/** + * jQuery Timepicker - v1.3.5 - 2016-07-10 + * http://timepicker.co + * + * Enhances standard form input fields helping users to select (or type) times. + * + * Copyright (c) 2016 Willington Vega; Licensed MIT, GPL + */ + +(function (factory) { + if ( typeof module === 'object' && typeof module.exports === 'object' ) { + factory(require('jquery'), window, document); + } else if (typeof jQuery !== 'undefined') { + factory(jQuery, window, document); + } +}(function($, window, document, undefined) { + (function() { + + function pad(str, ch, length) { + return (new Array(length + 1 - str.length).join(ch)) + str; + } + + function normalize() { + if (arguments.length === 1) { + var date = arguments[0]; + if (typeof date === 'string') { + date = $.fn.timepicker.parseTime(date); + } + return new Date(0, 0, 0, date.getHours(), date.getMinutes(), date.getSeconds()); + } else if (arguments.length === 3) { + return new Date(0, 0, 0, arguments[0], arguments[1], arguments[2]); + } else if (arguments.length === 2) { + return new Date(0, 0, 0, arguments[0], arguments[1], 0); + } else { + return new Date(0, 0, 0); + } + } + + $.TimePicker = function() { + var widget = this; + + widget.container = $('.ui-timepicker-container'); + widget.ui = widget.container.find('.ui-timepicker'); + + if (widget.container.length === 0) { + widget.container = $('
      ').addClass('ui-timepicker-container') + .addClass('ui-timepicker-hidden ui-helper-hidden') + .appendTo('body') + .hide(); + widget.ui = $( '
      ' ).addClass('ui-timepicker') + .addClass('ui-widget ui-widget-content ui-menu') + .addClass('ui-corner-all') + .appendTo(widget.container); + widget.viewport = $('
        ').addClass( 'ui-timepicker-viewport' ) + .appendTo( widget.ui ); + + if ($.fn.jquery >= '1.4.2') { + widget.ui.delegate('a', 'mouseenter.timepicker', function() { + // passing false instead of an instance object tells the function + // to use the current instance + widget.activate(false, $(this).parent()); + }).delegate('a', 'mouseleave.timepicker', function() { + widget.deactivate(false); + }).delegate('a', 'click.timepicker', function(event) { + event.preventDefault(); + widget.select(false, $(this).parent()); + }); + } + } + }; + + $.TimePicker.count = 0; + $.TimePicker.instance = function() { + if (!$.TimePicker._instance) { + $.TimePicker._instance = new $.TimePicker(); + } + return $.TimePicker._instance; + }; + + $.TimePicker.prototype = { + // extracted from from jQuery UI Core + // http://github,com/jquery/jquery-ui/blob/master/ui/jquery.ui.core.js + keyCode: { + ALT: 18, + BLOQ_MAYUS: 20, + CTRL: 17, + DOWN: 40, + END: 35, + ENTER: 13, + HOME: 36, + LEFT: 37, + NUMPAD_ENTER: 108, + PAGE_DOWN: 34, + PAGE_UP: 33, + RIGHT: 39, + SHIFT: 16, + TAB: 9, + UP: 38 + }, + + _items: function(i, startTime) { + var widget = this, ul = $('
          '), item = null, time, end; + + // interval should be a multiple of 60 if timeFormat is not + // showing minutes + if (i.options.timeFormat.indexOf('m') === -1 && i.options.interval % 60 !== 0) { + i.options.interval = Math.max(Math.round(i.options.interval / 60), 1) * 60; + } + + if (startTime) { + time = normalize(startTime); + } else if (i.options.startTime) { + time = normalize(i.options.startTime); + } else { + time = normalize(i.options.startHour, i.options.startMinutes); + } + + end = new Date(time.getTime() + 24 * 60 * 60 * 1000); + + while(time < end) { + if (widget._isValidTime(i, time)) { + item = $('
        • ').addClass('ui-menu-item').appendTo(ul); + $('').addClass('ui-corner-all').text($.fn.timepicker.formatTime(i.options.timeFormat, time)).appendTo(item); + item.data('time-value', time); + } + time = new Date(time.getTime() + i.options.interval * 60 * 1000); + } + + return ul.children(); + }, + + _isValidTime: function(i, time) { + var min = null, max = null; + + time = normalize(time); + + if (i.options.minTime !== null) { + min = normalize(i.options.minTime); + } else if (i.options.minHour !== null || i.options.minMinutes !== null) { + min = normalize(i.options.minHour, i.options.minMinutes); + } + + if (i.options.maxTime !== null) { + max = normalize(i.options.maxTime); + } else if (i.options.maxHour !== null || i.options.maxMinutes !== null) { + max = normalize(i.options.maxHour, i.options.maxMinutes); + } + + if (min !== null && max !== null) { + return time >= min && time <= max; + } else if (min !== null) { + return time >= min; + } else if (max !== null) { + return time <= max; + } + + return true; + }, + + _hasScroll: function() { + // fix for jQuery 1.6 new prop method + var m = typeof this.ui.prop !== 'undefined' ? 'prop' : 'attr'; + return this.ui.height() < this.ui[m]('scrollHeight'); + }, + + /** + * TODO: Write me! + * + * @param i + * @param direction + * @param edge + * */ + _move: function(i, direction, edge) { + var widget = this; + if (widget.closed()) { + widget.open(i); + } + if (!widget.active) { + widget.activate( i, widget.viewport.children( edge ) ); + return; + } + var next = widget.active[direction + 'All']('.ui-menu-item').eq(0); + if (next.length) { + widget.activate(i, next); + } else { + widget.activate( i, widget.viewport.children( edge ) ); + } + }, + + // + // protected methods + // + + register: function(node, options) { + var widget = this, i = {}; // timepicker instance object + + i.element = $(node); + + if (i.element.data('TimePicker')) { + return; + } + + i.options = $.metadata ? $.extend({}, options, i.element.metadata()) : $.extend({}, options); + i.widget = widget; + + // proxy functions for the exposed api methods + $.extend(i, { + next: function() {return widget.next(i) ;}, + previous: function() {return widget.previous(i) ;}, + first: function() { return widget.first(i) ;}, + last: function() { return widget.last(i) ;}, + selected: function() { return widget.selected(i) ;}, + open: function() { return widget.open(i) ;}, + close: function() { return widget.close(i) ;}, + closed: function() { return widget.closed(i) ;}, + destroy: function() { return widget.destroy(i) ;}, + + parse: function(str) { return widget.parse(i, str) ;}, + format: function(time, format) { return widget.format(i, time, format); }, + getTime: function() { return widget.getTime(i) ;}, + setTime: function(time, silent) { return widget.setTime(i, time, silent); }, + option: function(name, value) { return widget.option(i, name, value); } + }); + + widget._setDefaultTime(i); + widget._addInputEventsHandlers(i); + + i.element.data('TimePicker', i); + }, + + _setDefaultTime: function(i) { + if (i.options.defaultTime === 'now') { + i.setTime(normalize(new Date())); + } else if (i.options.defaultTime && i.options.defaultTime.getFullYear) { + i.setTime(normalize(i.options.defaultTime)); + } else if (i.options.defaultTime) { + i.setTime($.fn.timepicker.parseTime(i.options.defaultTime)); + } + }, + + _addInputEventsHandlers: function(i) { + var widget = this; + + i.element.bind('keydown.timepicker', function(event) { + switch (event.which || event.keyCode) { + case widget.keyCode.ENTER: + case widget.keyCode.NUMPAD_ENTER: + event.preventDefault(); + if (widget.closed()) { + i.element.trigger('change.timepicker'); + } else { + widget.select(i, widget.active); + } + break; + case widget.keyCode.UP: + i.previous(); + break; + case widget.keyCode.DOWN: + i.next(); + break; + default: + if (!widget.closed()) { + i.close(true); + } + break; + } + }).bind('focus.timepicker', function() { + i.open(); + }).bind('blur.timepicker', function() { + setTimeout(function() { + if (i.element.data('timepicker-user-clicked-outside')) { + i.close(); + } + }); + }).bind('change.timepicker', function() { + if (i.closed()) { + i.setTime($.fn.timepicker.parseTime(i.element.val())); + } + }); + }, + + select: function(i, item) { + var widget = this, instance = i === false ? widget.instance : i; + widget.setTime(instance, $.fn.timepicker.parseTime(item.children('a').text())); + widget.close(instance, true); + }, + + activate: function(i, item) { + var widget = this, instance = i === false ? widget.instance : i; + + if (instance !== widget.instance) { + return; + } else { + widget.deactivate(); + } + + if (widget._hasScroll()) { + var offset = item.offset().top - widget.ui.offset().top, + scroll = widget.ui.scrollTop(), + height = widget.ui.height(); + if (offset < 0) { + widget.ui.scrollTop(scroll + offset); + } else if (offset >= height) { + widget.ui.scrollTop(scroll + offset - height + item.height()); + } + } + + widget.active = item.eq(0).children('a').addClass('ui-state-hover') + .attr('id', 'ui-active-item') + .end(); + }, + + deactivate: function() { + var widget = this; + if (!widget.active) { return; } + widget.active.children('a').removeClass('ui-state-hover').removeAttr('id'); + widget.active = null; + }, + + /** + * _activate, _deactivate, first, last, next, previous, _move and + * _hasScroll were extracted from jQuery UI Menu + * http://github,com/jquery/jquery-ui/blob/menu/ui/jquery.ui.menu.js + */ + + // + // public methods + // + + next: function(i) { + if (this.closed() || this.instance === i) { + this._move(i, 'next', '.ui-menu-item:first'); + } + return i.element; + }, + + previous: function(i) { + if (this.closed() || this.instance === i) { + this._move(i, 'prev', '.ui-menu-item:last'); + } + return i.element; + }, + + first: function(i) { + if (this.instance === i) { + return this.active && this.active.prevAll('.ui-menu-item').length === 0; + } + return false; + }, + + last: function(i) { + if (this.instance === i) { + return this.active && this.active.nextAll('.ui-menu-item').length === 0; + } + return false; + }, + + selected: function(i) { + if (this.instance === i) { + return this.active ? this.active : null; + } + return null; + }, + + open: function(i) { + var widget = this, + selectedTime = i.getTime(), + arrange = i.options.dynamic && selectedTime; + + // return if dropdown is disabled + if (!i.options.dropdown) { return i.element; } + + // fix for issue https://github.com/wvega/timepicker/issues/56 + // idea from https://prototype.lighthouseapp.com/projects/8887/tickets/248-results-popup-from-ajaxautocompleter-disappear-when-user-clicks-on-scrollbars-in-ie6ie7 + i.element.data('timepicker-event-namespace', Math.random()); + + $(document).bind('click.timepicker-' + i.element.data('timepicker-event-namespace'), function(event) { + if (i.element.get(0) === event.target) { + i.element.data('timepicker-user-clicked-outside', false); + } else { + i.element.data('timepicker-user-clicked-outside', true).blur(); + } + }); + + // if a date is already selected and options.dynamic is true, + // arrange the items in the list so the first item is + // cronologically right after the selected date. + // TODO: set selectedTime + if (i.rebuild || !i.items || arrange) { + i.items = widget._items(i, arrange ? selectedTime : null); + } + + // remove old li elements keeping associated events, then append + // the new li elements to the ul + if (i.rebuild || widget.instance !== i || arrange) { + // handle menu events when using jQuery versions previous to + // 1.4.2 (thanks to Brian Link) + // http://github.com/wvega/timepicker/issues#issue/4 + if ($.fn.jquery < '1.4.2') { + widget.viewport.children().remove(); + widget.viewport.append(i.items); + widget.viewport.find('a').bind('mouseover.timepicker', function() { + widget.activate(i, $(this).parent()); + }).bind('mouseout.timepicker', function() { + widget.deactivate(i); + }).bind('click.timepicker', function(event) { + event.preventDefault(); + widget.select(i, $(this).parent()); + }); + } else { + widget.viewport.children().detach(); + widget.viewport.append(i.items); + } + } + + i.rebuild = false; + + // theme + widget.container.removeClass('ui-helper-hidden ui-timepicker-hidden ui-timepicker-standard ui-timepicker-corners').show(); + + switch (i.options.theme) { + case 'standard': + widget.container.addClass('ui-timepicker-standard'); + break; + case 'standard-rounded-corners': + widget.container.addClass('ui-timepicker-standard ui-timepicker-corners'); + break; + default: + break; + } + + /* resize ui */ + + // we are hiding the scrollbar in the dropdown menu adding a 40px + // padding to the wrapper element making the scrollbar appear in the + // part of the wrapper that's hidden by the container (a DIV). + if ( ! widget.container.hasClass( 'ui-timepicker-no-scrollbar' ) && ! i.options.scrollbar ) { + widget.container.addClass( 'ui-timepicker-no-scrollbar' ); + widget.viewport.css( { paddingRight: 40 } ); + } + + var containerDecorationHeight = widget.container.outerHeight() - widget.container.height(), + zindex = i.options.zindex ? i.options.zindex : i.element.offsetParent().css( 'z-index' ), + elementOffset = i.element.offset(); + + // position the container right below the element, or as close to as possible. + widget.container.css( { + top: elementOffset.top + i.element.outerHeight(), + left: elementOffset.left + } ); + + // then show the container so that the browser can consider the timepicker's + // height to calculate the page's total height and decide if adding scrollbars + // is necessary. + widget.container.show(); + + // now we need to calculate the element offset and position the container again. + // If the browser added scrollbars, the container's original position is not aligned + // with the element's final position. This step fixes that problem. + widget.container.css( { + left: i.element.offset().left, + height: widget.ui.outerHeight() + containerDecorationHeight, + width: i.element.outerWidth(), + zIndex: zindex, + cursor: 'default' + } ); + + var calculatedWidth = widget.container.width() - ( widget.ui.outerWidth() - widget.ui.width() ); + + // hardcode ui, viewport and item's width. I couldn't get it to work using CSS only + widget.ui.css( { width: calculatedWidth } ); + widget.viewport.css( { width: calculatedWidth } ); + i.items.css( { width: calculatedWidth } ); + + // XXX: what's this line doing here? + widget.instance = i; + + // try to match input field's current value with an item in the + // dropdown + if (selectedTime) { + i.items.each(function() { + var item = $(this), time; + + if ($.fn.jquery < '1.4.2') { + time = $.fn.timepicker.parseTime(item.find('a').text()); + } else { + time = item.data('time-value'); + } + + if (time.getTime() === selectedTime.getTime()) { + widget.activate(i, item); + return false; + } + return true; + }); + } else { + widget.deactivate(i); + } + + // don't break the chain + return i.element; + }, + + close: function(i) { + var widget = this; + + if (widget.instance === i) { + widget.container.addClass('ui-helper-hidden ui-timepicker-hidden').hide(); + widget.ui.scrollTop(0); + widget.ui.children().removeClass('ui-state-hover'); + } + + $(document).unbind('click.timepicker-' + i.element.data('timepicker-event-namespace')); + + return i.element; + }, + + closed: function() { + return this.ui.is(':hidden'); + }, + + destroy: function(i) { + var widget = this; + widget.close(i, true); + return i.element.unbind('.timepicker').data('TimePicker', null); + }, + + // + + parse: function(i, str) { + return $.fn.timepicker.parseTime(str); + }, + + format: function(i, time, format) { + format = format || i.options.timeFormat; + return $.fn.timepicker.formatTime(format, time); + }, + + getTime: function(i) { + var widget = this, + current = $.fn.timepicker.parseTime(i.element.val()); + + // if current value is not valid, we return null. + // stored Date object is ignored, because the current value + // (valid or invalid) always takes priority + if (current instanceof Date && !widget._isValidTime(i, current)) { + return null; + } else if (current instanceof Date && i.selectedTime) { + // if the textfield's value and the stored Date object + // have the same representation using current format + // we prefer the stored Date object to avoid unnecesary + // lost of precision. + if (i.format(current) === i.format(i.selectedTime)) { + return i.selectedTime; + } else { + return current; + } + } else if (current instanceof Date) { + return current; + } else { + return null; + } + }, + + setTime: function(i, time, silent) { + var widget = this, previous = i.selectedTime; + + if (typeof time === 'string') { + time = i.parse(time); + } + + if (time && time.getMinutes && widget._isValidTime(i, time)) { + time = normalize(time); + i.selectedTime = time; + i.element.val(i.format(time, i.options.timeFormat)); + + // TODO: add documentaion about setTime being chainable + if (silent) { return i; } + } else { + i.selectedTime = null; + } + + // custom change event and change callback + // TODO: add documentation about this event + if (previous !== null || i.selectedTime !== null) { + i.element.trigger('time-change', [time]); + if ($.isFunction(i.options.change)) { + i.options.change.apply(i.element, [time]); + } + } + + return i.element; + }, + + option: function(i, name, value) { + if (typeof value === 'undefined') { + return i.options[name]; + } + + var time = i.getTime(), + options, destructive; + + if (typeof name === 'string') { + options = {}; + options[name] = value; + } else { + options = name; + } + + // some options require rebuilding the dropdown items + destructive = ['minHour', 'minMinutes', 'minTime', + 'maxHour', 'maxMinutes', 'maxTime', + 'startHour', 'startMinutes', 'startTime', + 'timeFormat', 'interval', 'dropdown']; + + + $.each(options, function(name) { + i.options[name] = options[name]; + i.rebuild = i.rebuild || $.inArray(name, destructive) > -1; + }); + + if (i.rebuild) { + i.setTime(time); + } + } + }; + + $.TimePicker.defaults = { + timeFormat: 'hh:mm p', + minHour: null, + minMinutes: null, + minTime: null, + maxHour: null, + maxMinutes: null, + maxTime: null, + startHour: null, + startMinutes: null, + startTime: null, + interval: 30, + dynamic: true, + theme: 'standard', + zindex: null, + dropdown: true, + scrollbar: false, + // callbacks + change: function(/*time*/) {} + }; + + $.TimePicker.methods = { + chainable: [ + 'next', + 'previous', + 'open', + 'close', + 'destroy', + 'setTime' + ] + }; + + $.fn.timepicker = function(options) { + // support calling API methods using the following syntax: + // $(...).timepicker('parse', '11p'); + if (typeof options === 'string') { + var args = Array.prototype.slice.call(arguments, 1), + method, result; + + // chainable API methods + if (options === 'option' && arguments.length > 2) { + method = 'each'; + } else if ($.inArray(options, $.TimePicker.methods.chainable) !== -1) { + method = 'each'; + // API methods that return a value + } else { + method = 'map'; + } + + result = this[method](function() { + var element = $(this), i = element.data('TimePicker'); + if (typeof i === 'object') { + return i[options].apply(i, args); + } + }); + + if (method === 'map' && this.length === 1) { + return $.makeArray(result).shift(); + } else if (method === 'map') { + return $.makeArray(result); + } else { + return result; + } + } + + // calling the constructor again on a jQuery object with a single + // element returns a reference to a TimePicker object. + if (this.length === 1 && this.data('TimePicker')) { + return this.data('TimePicker'); + } + + var globals = $.extend({}, $.TimePicker.defaults, options); + + return this.each(function() { + $.TimePicker.instance().register(this, globals); + }); + }; + + /** + * TODO: documentation + */ + $.fn.timepicker.formatTime = function(format, time) { + var hours = time.getHours(), + hours12 = hours % 12, + minutes = time.getMinutes(), + seconds = time.getSeconds(), + replacements = { + hh: pad((hours12 === 0 ? 12 : hours12).toString(), '0', 2), + HH: pad(hours.toString(), '0', 2), + mm: pad(minutes.toString(), '0', 2), + ss: pad(seconds.toString(), '0', 2), + h: (hours12 === 0 ? 12 : hours12), + H: hours, + m: minutes, + s: seconds, + p: hours > 11 ? 'PM' : 'AM' + }, + str = format, k = ''; + for (k in replacements) { + if (replacements.hasOwnProperty(k)) { + str = str.replace(new RegExp(k,'g'), replacements[k]); + } + } + // replacements is not guaranteed to be order and the 'p' can cause problems + str = str.replace(new RegExp('a','g'), hours > 11 ? 'pm' : 'am'); + return str; + }; + + /** + * Convert a string representing a given time into a Date object. + * + * The Date object will have attributes others than hours, minutes and + * seconds set to current local time values. The function will return + * false if given string can't be converted. + * + * If there is an 'a' in the string we set am to true, if there is a 'p' + * we set pm to true, if both are present only am is setted to true. + * + * All non-digit characters are removed from the string before trying to + * parse the time. + * + * '' can't be converted and the function returns false. + * '1' is converted to 01:00:00 am + * '11' is converted to 11:00:00 am + * '111' is converted to 01:11:00 am + * '1111' is converted to 11:11:00 am + * '11111' is converted to 01:11:11 am + * '111111' is converted to 11:11:11 am + * + * Only the first six (or less) characters are considered. + * + * Special case: + * + * When hours is greater than 24 and the last digit is less or equal than 6, and minutes + * and seconds are less or equal than 60, we append a trailing zero and + * start parsing process again. Examples: + * + * '95' is treated as '950' and converted to 09:50:00 am + * '46' is treated as '460' and converted to 05:00:00 am + * '57' can't be converted and the function returns false. + * + * For a detailed list of supported formats check the unit tests at + * http://github.com/wvega/timepicker/tree/master/tests/ + */ + $.fn.timepicker.parseTime = (function() { + var patterns = [ + // 1, 12, 123, 1234, 12345, 123456 + [/^(\d+)$/, '$1'], + // :1, :2, :3, :4 ... :9 + [/^:(\d)$/, '$10'], + // :1, :12, :123, :1234 ... + [/^:(\d+)/, '$1'], + // 6:06, 5:59, 5:8 + [/^(\d):([7-9])$/, '0$10$2'], + [/^(\d):(\d\d)$/, '$1$2'], + [/^(\d):(\d{1,})$/, '0$1$20'], + // 10:8, 10:10, 10:34 + [/^(\d\d):([7-9])$/, '$10$2'], + [/^(\d\d):(\d)$/, '$1$20'], + [/^(\d\d):(\d*)$/, '$1$2'], + // 123:4, 1234:456 + [/^(\d{3,}):(\d)$/, '$10$2'], + [/^(\d{3,}):(\d{2,})/, '$1$2'], + // + [/^(\d):(\d):(\d)$/, '0$10$20$3'], + [/^(\d{1,2}):(\d):(\d\d)/, '$10$2$3'] + ], + length = patterns.length; + + return function(str) { + var time = normalize(new Date()), + am = false, pm = false, h = false, m = false, s = false; + + if (typeof str === 'undefined' || !str.toLowerCase) { return null; } + + str = str.toLowerCase(); + am = /a/.test(str); + pm = am ? false : /p/.test(str); + str = str.replace(/[^0-9:]/g, '').replace(/:+/g, ':'); + + for (var k = 0; k < length; k = k + 1) { + if (patterns[k][0].test(str)) { + str = str.replace(patterns[k][0], patterns[k][1]); + break; + } + } + str = str.replace(/:/g, ''); + + if (str.length === 1) { + h = str; + } else if (str.length === 2) { + h = str; + } else if (str.length === 3 || str.length === 5) { + h = str.substr(0, 1); + m = str.substr(1, 2); + s = str.substr(3, 2); + } else if (str.length === 4 || str.length > 5) { + h = str.substr(0, 2); + m = str.substr(2, 2); + s = str.substr(4, 2); + } + + if (str.length > 0 && str.length < 5) { + if (str.length < 3) { + m = 0; + } + s = 0; + } + + if (h === false || m === false || s === false) { + return false; + } + + h = parseInt(h, 10); + m = parseInt(m, 10); + s = parseInt(s, 10); + + if (am && h === 12) { + h = 0; + } else if (pm && h < 12) { + h = h + 12; + } + + if (h > 24) { + if (str.length >= 6) { + return $.fn.timepicker.parseTime(str.substr(0,5)); + } else { + return $.fn.timepicker.parseTime(str + '0' + (am ? 'a' : '') + (pm ? 'p' : '')); + } + } else { + time.setHours(h, m, s); + return time; + } + }; + })(); + })(); +})); diff --git a/builder_templates/174/scripts/jquery-timepicker/jquery.timepicker.min.css b/builder_templates/174/scripts/jquery-timepicker/jquery.timepicker.min.css new file mode 100644 index 0000000..bb60921 --- /dev/null +++ b/builder_templates/174/scripts/jquery-timepicker/jquery.timepicker.min.css @@ -0,0 +1,11 @@ +/** + * jQuery Timepicker - v1.3.5 - 2016-07-10 + * http://timepicker.co + * + * Enhances standard form input fields helping users to select (or type) times. + * + * Copyright (c) 2016 Willington Vega; Licensed MIT, GPL + */ + + +.ui-timepicker-container{position:absolute;overflow:hidden;box-sizing:border-box}.ui-timepicker{box-sizing:content-box;display:block;height:205px;list-style:none outside none;margin:0;padding:0 1px;text-align:center}.ui-timepicker-viewport{box-sizing:content-box;display:block;height:205px;margin:0;padding:0;overflow:auto;overflow-x:hidden}.ui-timepicker-standard{font-family:Verdana,Arial,sans-serif;font-size:1.1em;background-color:#FFF;border:1px solid #AAA;color:#222;margin:0;padding:2px}.ui-timepicker-standard a{border:1px solid transparent;color:#222;display:block;padding:.2em .4em;text-decoration:none}.ui-timepicker-standard .ui-state-hover{background-color:#DADADA;border:1px solid #999;font-weight:400;color:#212121}.ui-timepicker-standard .ui-menu-item{margin:0;padding:0}.ui-timepicker-corners,.ui-timepicker-corners .ui-corner-all{-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px}.ui-timepicker-hidden{display:none}.ui-timepicker-no-scrollbar .ui-timepicker{border:0} \ No newline at end of file diff --git a/builder_templates/174/scripts/jquery-timepicker/jquery.timepicker.min.js b/builder_templates/174/scripts/jquery-timepicker/jquery.timepicker.min.js new file mode 100644 index 0000000..fe895a0 --- /dev/null +++ b/builder_templates/174/scripts/jquery-timepicker/jquery.timepicker.min.js @@ -0,0 +1,10 @@ +/** + * jQuery Timepicker - v1.3.5 - 2016-07-10 + * http://timepicker.co + * + * Enhances standard form input fields helping users to select (or type) times. + * + * Copyright (c) 2016 Willington Vega; Licensed MIT, GPL + */ + +!function(a){"object"==typeof module&&"object"==typeof module.exports?a(require("jquery"),window,document):"undefined"!=typeof jQuery&&a(jQuery,window,document)}(function(a,b,c,d){!function(){function b(a,b,c){return new Array(c+1-a.length).join(b)+a}function d(){if(1===arguments.length){var b=arguments[0];return"string"==typeof b&&(b=a.fn.timepicker.parseTime(b)),new Date(0,0,0,b.getHours(),b.getMinutes(),b.getSeconds())}return 3===arguments.length?new Date(0,0,0,arguments[0],arguments[1],arguments[2]):2===arguments.length?new Date(0,0,0,arguments[0],arguments[1],0):new Date(0,0,0)}a.TimePicker=function(){var b=this;b.container=a(".ui-timepicker-container"),b.ui=b.container.find(".ui-timepicker"),0===b.container.length&&(b.container=a("
          ").addClass("ui-timepicker-container").addClass("ui-timepicker-hidden ui-helper-hidden").appendTo("body").hide(),b.ui=a("
          ").addClass("ui-timepicker").addClass("ui-widget ui-widget-content ui-menu").addClass("ui-corner-all").appendTo(b.container),b.viewport=a("
            ").addClass("ui-timepicker-viewport").appendTo(b.ui),a.fn.jquery>="1.4.2"&&b.ui.delegate("a","mouseenter.timepicker",function(){b.activate(!1,a(this).parent())}).delegate("a","mouseleave.timepicker",function(){b.deactivate(!1)}).delegate("a","click.timepicker",function(c){c.preventDefault(),b.select(!1,a(this).parent())}))},a.TimePicker.count=0,a.TimePicker.instance=function(){return a.TimePicker._instance||(a.TimePicker._instance=new a.TimePicker),a.TimePicker._instance},a.TimePicker.prototype={keyCode:{ALT:18,BLOQ_MAYUS:20,CTRL:17,DOWN:40,END:35,ENTER:13,HOME:36,LEFT:37,NUMPAD_ENTER:108,PAGE_DOWN:34,PAGE_UP:33,RIGHT:39,SHIFT:16,TAB:9,UP:38},_items:function(b,c){var e,f,g=this,h=a("
              "),i=null;for(-1===b.options.timeFormat.indexOf("m")&&b.options.interval%60!==0&&(b.options.interval=60*Math.max(Math.round(b.options.interval/60),1)),e=c?d(c):b.options.startTime?d(b.options.startTime):d(b.options.startHour,b.options.startMinutes),f=new Date(e.getTime()+864e5);f>e;)g._isValidTime(b,e)&&(i=a("
            • ").addClass("ui-menu-item").appendTo(h),a("").addClass("ui-corner-all").text(a.fn.timepicker.formatTime(b.options.timeFormat,e)).appendTo(i),i.data("time-value",e)),e=new Date(e.getTime()+60*b.options.interval*1e3);return h.children()},_isValidTime:function(a,b){var c=null,e=null;return b=d(b),null!==a.options.minTime?c=d(a.options.minTime):(null!==a.options.minHour||null!==a.options.minMinutes)&&(c=d(a.options.minHour,a.options.minMinutes)),null!==a.options.maxTime?e=d(a.options.maxTime):(null!==a.options.maxHour||null!==a.options.maxMinutes)&&(e=d(a.options.maxHour,a.options.maxMinutes)),null!==c&&null!==e?b>=c&&e>=b:null!==c?b>=c:null!==e?e>=b:!0},_hasScroll:function(){var a="undefined"!=typeof this.ui.prop?"prop":"attr";return this.ui.height()e?c.ui.scrollTop(f+e):e>=g&&c.ui.scrollTop(f+e-g+b.height())}c.active=b.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-item").end()}},deactivate:function(){var a=this;a.active&&(a.active.children("a").removeClass("ui-state-hover").removeAttr("id"),a.active=null)},next:function(a){return(this.closed()||this.instance===a)&&this._move(a,"next",".ui-menu-item:first"),a.element},previous:function(a){return(this.closed()||this.instance===a)&&this._move(a,"prev",".ui-menu-item:last"),a.element},first:function(a){return this.instance===a?this.active&&0===this.active.prevAll(".ui-menu-item").length:!1},last:function(a){return this.instance===a?this.active&&0===this.active.nextAll(".ui-menu-item").length:!1},selected:function(a){return this.instance===a&&this.active?this.active:null},open:function(b){var d=this,e=b.getTime(),f=b.options.dynamic&&e;if(!b.options.dropdown)return b.element;switch(b.element.data("timepicker-event-namespace",Math.random()),a(c).bind("click.timepicker-"+b.element.data("timepicker-event-namespace"),function(a){b.element.get(0)===a.target?b.element.data("timepicker-user-clicked-outside",!1):b.element.data("timepicker-user-clicked-outside",!0).blur()}),(b.rebuild||!b.items||f)&&(b.items=d._items(b,f?e:null)),(b.rebuild||d.instance!==b||f)&&(a.fn.jquery<"1.4.2"?(d.viewport.children().remove(),d.viewport.append(b.items),d.viewport.find("a").bind("mouseover.timepicker",function(){d.activate(b,a(this).parent())}).bind("mouseout.timepicker",function(){d.deactivate(b)}).bind("click.timepicker",function(c){c.preventDefault(),d.select(b,a(this).parent())})):(d.viewport.children().detach(),d.viewport.append(b.items))),b.rebuild=!1,d.container.removeClass("ui-helper-hidden ui-timepicker-hidden ui-timepicker-standard ui-timepicker-corners").show(),b.options.theme){case"standard":d.container.addClass("ui-timepicker-standard");break;case"standard-rounded-corners":d.container.addClass("ui-timepicker-standard ui-timepicker-corners")}d.container.hasClass("ui-timepicker-no-scrollbar")||b.options.scrollbar||(d.container.addClass("ui-timepicker-no-scrollbar"),d.viewport.css({paddingRight:40}));var g=d.container.outerHeight()-d.container.height(),h=b.options.zindex?b.options.zindex:b.element.offsetParent().css("z-index"),i=b.element.offset();d.container.css({top:i.top+b.element.outerHeight(),left:i.left}),d.container.show(),d.container.css({left:b.element.offset().left,height:d.ui.outerHeight()+g,width:b.element.outerWidth(),zIndex:h,cursor:"default"});var j=d.container.width()-(d.ui.outerWidth()-d.ui.width());return d.ui.css({width:j}),d.viewport.css({width:j}),b.items.css({width:j}),d.instance=b,e?b.items.each(function(){var c,f=a(this);return c=a.fn.jquery<"1.4.2"?a.fn.timepicker.parseTime(f.find("a").text()):f.data("time-value"),c.getTime()===e.getTime()?(d.activate(b,f),!1):!0}):d.deactivate(b),b.element},close:function(b){var d=this;return d.instance===b&&(d.container.addClass("ui-helper-hidden ui-timepicker-hidden").hide(),d.ui.scrollTop(0),d.ui.children().removeClass("ui-state-hover")),a(c).unbind("click.timepicker-"+b.element.data("timepicker-event-namespace")),b.element},closed:function(){return this.ui.is(":hidden")},destroy:function(a){var b=this;return b.close(a,!0),a.element.unbind(".timepicker").data("TimePicker",null)},parse:function(b,c){return a.fn.timepicker.parseTime(c)},format:function(b,c,d){return d=d||b.options.timeFormat,a.fn.timepicker.formatTime(d,c)},getTime:function(b){var c=this,d=a.fn.timepicker.parseTime(b.element.val());return d instanceof Date&&!c._isValidTime(b,d)?null:d instanceof Date&&b.selectedTime?b.format(d)===b.format(b.selectedTime)?b.selectedTime:d:d instanceof Date?d:null},setTime:function(b,c,e){var f=this,g=b.selectedTime;if("string"==typeof c&&(c=b.parse(c)),c&&c.getMinutes&&f._isValidTime(b,c)){if(c=d(c),b.selectedTime=c,b.element.val(b.format(c,b.options.timeFormat)),e)return b}else b.selectedTime=null;return(null!==g||null!==b.selectedTime)&&(b.element.trigger("time-change",[c]),a.isFunction(b.options.change)&&b.options.change.apply(b.element,[c])),b.element},option:function(b,c,d){if("undefined"==typeof d)return b.options[c];var e,f,g=b.getTime();"string"==typeof c?(e={},e[c]=d):e=c,f=["minHour","minMinutes","minTime","maxHour","maxMinutes","maxTime","startHour","startMinutes","startTime","timeFormat","interval","dropdown"],a.each(e,function(c){b.options[c]=e[c],b.rebuild=b.rebuild||a.inArray(c,f)>-1}),b.rebuild&&b.setTime(g)}},a.TimePicker.defaults={timeFormat:"hh:mm p",minHour:null,minMinutes:null,minTime:null,maxHour:null,maxMinutes:null,maxTime:null,startHour:null,startMinutes:null,startTime:null,interval:30,dynamic:!0,theme:"standard",zindex:null,dropdown:!0,scrollbar:!1,change:function(){}},a.TimePicker.methods={chainable:["next","previous","open","close","destroy","setTime"]},a.fn.timepicker=function(b){if("string"==typeof b){var c,d,e=Array.prototype.slice.call(arguments,1);return c="option"===b&&arguments.length>2?"each":-1!==a.inArray(b,a.TimePicker.methods.chainable)?"each":"map",d=this[c](function(){var c=a(this),d=c.data("TimePicker");return"object"==typeof d?d[b].apply(d,e):void 0}),"map"===c&&1===this.length?a.makeArray(d).shift():"map"===c?a.makeArray(d):d}if(1===this.length&&this.data("TimePicker"))return this.data("TimePicker");var f=a.extend({},a.TimePicker.defaults,b);return this.each(function(){a.TimePicker.instance().register(this,f)})},a.fn.timepicker.formatTime=function(a,c){var d=c.getHours(),e=d%12,f=c.getMinutes(),g=c.getSeconds(),h={hh:b((0===e?12:e).toString(),"0",2),HH:b(d.toString(),"0",2),mm:b(f.toString(),"0",2),ss:b(g.toString(),"0",2),h:0===e?12:e,H:d,m:f,s:g,p:d>11?"PM":"AM"},i=a,j="";for(j in h)h.hasOwnProperty(j)&&(i=i.replace(new RegExp(j,"g"),h[j]));return i=i.replace(new RegExp("a","g"),d>11?"pm":"am")},a.fn.timepicker.parseTime=function(){var b=[[/^(\d+)$/,"$1"],[/^:(\d)$/,"$10"],[/^:(\d+)/,"$1"],[/^(\d):([7-9])$/,"0$10$2"],[/^(\d):(\d\d)$/,"$1$2"],[/^(\d):(\d{1,})$/,"0$1$20"],[/^(\d\d):([7-9])$/,"$10$2"],[/^(\d\d):(\d)$/,"$1$20"],[/^(\d\d):(\d*)$/,"$1$2"],[/^(\d{3,}):(\d)$/,"$10$2"],[/^(\d{3,}):(\d{2,})/,"$1$2"],[/^(\d):(\d):(\d)$/,"0$10$20$3"],[/^(\d{1,2}):(\d):(\d\d)/,"$10$2$3"]],c=b.length;return function(e){var f=d(new Date),g=!1,h=!1,i=!1,j=!1,k=!1;if("undefined"==typeof e||!e.toLowerCase)return null;e=e.toLowerCase(),g=/a/.test(e),h=g?!1:/p/.test(e),e=e.replace(/[^0-9:]/g,"").replace(/:+/g,":");for(var l=0;c>l;l+=1)if(b[l][0].test(e)){e=e.replace(b[l][0],b[l][1]);break}return e=e.replace(/:/g,""),1===e.length?i=e:2===e.length?i=e:3===e.length||5===e.length?(i=e.substr(0,1),j=e.substr(1,2),k=e.substr(3,2)):(4===e.length||e.length>5)&&(i=e.substr(0,2),j=e.substr(2,2),k=e.substr(4,2)),e.length>0&&e.length<5&&(e.length<3&&(j=0),k=0),i===!1||j===!1||k===!1?!1:(i=parseInt(i,10),j=parseInt(j,10),k=parseInt(k,10),g&&12===i?i=0:h&&12>i&&(i+=12),i>24?e.length>=6?a.fn.timepicker.parseTime(e.substr(0,5)):a.fn.timepicker.parseTime(e+"0"+(g?"a":"")+(h?"p":"")):(f.setHours(i,j,k),f))}}()}()}); \ No newline at end of file diff --git a/builder_templates/174/scripts/jquery-ui/images/ui-bg_flat_0_aaaaaa_40x100.png b/builder_templates/174/scripts/jquery-ui/images/ui-bg_flat_0_aaaaaa_40x100.png new file mode 100644 index 0000000000000000000000000000000000000000..f812adf03fdbf385e90bb4100ebb3d4344c398ac GIT binary patch literal 251 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F1SA+{?>A)!QY`6?zK#qG8~eHcB(ehe3dtTp zz6=aiY77hwEes65fI}#<1A|b@qdh1Za`RI%(<*UmP)Vr;x{!gv)78&qol`;+ E06XA4NdN!< literal 0 HcmV?d00001 diff --git a/builder_templates/174/scripts/jquery-ui/images/ui-bg_flat_75_ffffff_40x100.png b/builder_templates/174/scripts/jquery-ui/images/ui-bg_flat_75_ffffff_40x100.png new file mode 100644 index 0000000000000000000000000000000000000000..ef7bb271fcd1c53e8b3f5cc168cdbc51d5ac220b GIT binary patch literal 247 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F2qYNp$opRhQY`6?zK#qG8~eHcB(ehe3dtTp zz6=aiY77hwEes65fIo=IO>_+n zbPdfy3@ofn46O`Jbq!3c3=BdokM^Kw$jwj5OsmALK_#UY=sE@lPgg&ebxsLQ0B|Qh AEdT%j literal 0 HcmV?d00001 diff --git a/builder_templates/174/scripts/jquery-ui/images/ui-bg_glass_55_fbf9ee_1x400.png b/builder_templates/174/scripts/jquery-ui/images/ui-bg_glass_55_fbf9ee_1x400.png new file mode 100644 index 0000000000000000000000000000000000000000..190010335e03966faf32c641209d50defaae7959 GIT binary patch literal 374 zcmeAS@N?(olHy`uVBq!ia0vp^j6gI&fCzz`*b-fq}tl1_Oh5!JJ)zHb4osByV?@|6srw@%;`^ zq}S8MF~sBe+w+QCOpYQA59{Y83FI_)Y*@_ZC}CL5@j7gEq(SE0-xbUUn=4vH%eo&+ zAABV0a@afm$7TP0FPB+zGf1>0O5A6=I_1@@JGI~5#eHD$N<7iC)*$I!nUmua6%n1g zfipHNQi?Y*`FQyRzwMUb!ep^Gm9BfYd46CC=VG?Wc6@Q+e|P3#gC6-$Y&MB1!5wcZ z^?(jjEpd$~Nl7e8wMs5Z1yT$~28JfOh6cKZW+4U^Rwjm4hNij(CRPRpp_WH`P&DM` ar(~v8;?|&&QVR?m1_n=8KbLh*2~7aKRDUJ_ literal 0 HcmV?d00001 diff --git a/builder_templates/174/scripts/jquery-ui/images/ui-bg_glass_65_ffffff_1x400.png b/builder_templates/174/scripts/jquery-ui/images/ui-bg_glass_65_ffffff_1x400.png new file mode 100644 index 0000000000000000000000000000000000000000..2d1bfda061dae64d82cda3d96a1d030f5e63d91c GIT binary patch literal 246 zcmeAS@N?(olHy`uVBq!ia0vp^j6gI2NH8$CE1Q=ADVB6cUq=Rpjs4tz5?O(Kg=CK) zUj~LMH3o);76yi2K%s^g3=E|P3=FRl7#OT(FffQ0%-I!a1C(G&@^*J&V7%KUyadP- z^mK6y(Kw%+ks#5!xIpAl1H;se6O5NlHf91PRZCnWN>UO_QmvAUQh^kMk%6I!uAzah zp;?H5g_Vh+m7%Gwfr*uYL8#@?9uy6^`6-!cmAEyiq|^c($H3s}>gTe~DWM4fTR}YO literal 0 HcmV?d00001 diff --git a/builder_templates/174/scripts/jquery-ui/images/ui-bg_glass_75_dadada_1x400.png b/builder_templates/174/scripts/jquery-ui/images/ui-bg_glass_75_dadada_1x400.png new file mode 100644 index 0000000000000000000000000000000000000000..8966740f50d5ca5eadc4f6447c5f4e77b2f7229f GIT binary patch literal 301 zcmeAS@N?(olHy`uVBq!ia0vp^j6gI&0LWmFTHNUZq*&4&eH|GXHuiJ>Nn{1`6_P!I zd>I(3)EF2VS{N990fib~Fff!FFfhDIU|_JC!N4G1FlSew4N!t9$=luK|9_FyhG&61 z4^J1z5R22v6~E>uiu5Y2S{%JyBCp!;PGRy6>C*-IN{9d2^Y9x>$kirP1fBcv#qe;k zeVh1aiMY6=iqdHhOEnL_?p&N9BG}BZFMP3Tkl4l=pqZ*At`Q|Ei6yC4$wjF^iowXh z&_vhJK-bVL#K6MJ#L&vnRM)`7%D^Dh@@Nl=hTQy=%(P0}8dOqhfqr6O@O1TaS?83{ F1OV|4STFzp literal 0 HcmV?d00001 diff --git a/builder_templates/174/scripts/jquery-ui/images/ui-bg_glass_75_e6e6e6_1x400.png b/builder_templates/174/scripts/jquery-ui/images/ui-bg_glass_75_e6e6e6_1x400.png new file mode 100644 index 0000000000000000000000000000000000000000..7756b9c31f03987bdee96ed0764bb22a3a6703c2 GIT binary patch literal 301 zcmeAS@N?(olHy`uVBq!ia0vp^j6gI&0LWmFTHNUZq*&4&eH|GXHuiJ>Nn{1`6_P!I zd>I(3)EF2VS{N990fib~Fff!FFfhDIU|_JC!N4G1FlSew4N!t9$=luK|9_FyhG&61 z4^J1z5R22v71NI!bh!y$y|_7@=X|fky!(k2hLazrsvTZ!(RN*tCqGHz-ZG;--Dwr_ z2RVJ?#5JNMC9x#cD!C{XNHG{0 z7@FuB8t59Dg&0^^nHX9bn(7*uSQ!|ES|05|(U6;;l9^VCTZ2kUEznO444$rjF6*2U FngBd(SUvy% literal 0 HcmV?d00001 diff --git a/builder_templates/174/scripts/jquery-ui/images/ui-bg_glass_95_fef1ec_1x400.png b/builder_templates/174/scripts/jquery-ui/images/ui-bg_glass_95_fef1ec_1x400.png new file mode 100644 index 0000000000000000000000000000000000000000..79c731dbd4af1beefa68903ce79adc792a9ca86a GIT binary patch literal 371 zcmeAS@N?(olHy`uVBq!ia0vp^j6gI&fCzz`*b-fq}tl1_Oh5!JJ)zHb4osByV?@|6srw@%;`^ zq|4LAF~sBe+w&WF851SiKR*92CiFZ;F-)NM8lRVP!~uqB7cO+W)Cx;9@am=gusbQ+ z`Oc1YV`IRctLrOPwQs37^G=wV;dr6U^V=7;mC7=moK-ky(xx(^b literal 0 HcmV?d00001 diff --git a/builder_templates/174/scripts/jquery-ui/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/builder_templates/174/scripts/jquery-ui/images/ui-bg_highlight-soft_75_cccccc_1x100.png new file mode 100644 index 0000000000000000000000000000000000000000..d52beb90429be6403c88a37840a789f374a47b22 GIT binary patch literal 319 zcmeAS@N?(olHy`uVBq!ia0vp^j6j?s03;ZUuHXC*q*&4&eH|GXHuiJ>Nn{1`6_P!I zd>I(3)EF2VS{N990fib~Fff!FFfhDIU|_JC!N4G1FlSew4N!t9$=luK|9_FyhG&7i zC{Gv15Q)pl6*aw$ei_V%7k}sJ{~{sx%*ZAqc}M)g0{!lXc{+z>dE27nCGy^-@3?rj zz;@2VSwe@WKIWNTZWNQ1x+CITK_b)PpK5Kz?2_m1C0V>aQ1I*wgA&8WY4cu9jjUz^ z+M-(G8c~vxSdwa$T$Bo=7>o=IO>_+nbPdfy3@ofn46O`Jbq!3c3=BdokM^Kw$jwj5 YOsmALK_#UY=t~9$Pgg&ebxsLQ0JLjl7XSbN literal 0 HcmV?d00001 diff --git a/builder_templates/174/scripts/jquery-ui/images/ui-icons_222222_256x240.png b/builder_templates/174/scripts/jquery-ui/images/ui-icons_222222_256x240.png new file mode 100644 index 0000000000000000000000000000000000000000..688e641c35cf5161bf1760eacad94f206db59fa0 GIT binary patch literal 7006 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 HqGJ9BtnP`> literal 0 HcmV?d00001 diff --git a/builder_templates/174/scripts/jquery-ui/images/ui-icons_454545_256x240.png b/builder_templates/174/scripts/jquery-ui/images/ui-icons_454545_256x240.png new file mode 100644 index 0000000000000000000000000000000000000000..0de6aab0a292f967ae95e64e4c0e33c55a77ffdc GIT binary patch literal 7071 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 JsgyDa`aiofL)icT literal 0 HcmV?d00001 diff --git a/builder_templates/174/scripts/jquery-ui/images/ui-icons_888888_256x240.png b/builder_templates/174/scripts/jquery-ui/images/ui-icons_888888_256x240.png new file mode 100644 index 0000000000000000000000000000000000000000..8407fe34f1fd38c231aa22a50a035858bc2a5f15 GIT binary patch literal 7092 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 k5H+JOfF7z4z1i%4c0|fx`SpdN7 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 u`{MB= 0 ) && focusable( element, !isTabIndexNaN ); + } +}); + +// support: jQuery <1.8 +if ( !$( "" ).outerWidth( 1 ).jquery ) { + $.each( [ "Width", "Height" ], function( i, name ) { + var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ], + type = name.toLowerCase(), + orig = { + innerWidth: $.fn.innerWidth, + innerHeight: $.fn.innerHeight, + outerWidth: $.fn.outerWidth, + outerHeight: $.fn.outerHeight + }; + + function reduce( elem, size, border, margin ) { + $.each( side, function() { + size -= parseFloat( $.css( elem, "padding" + this ) ) || 0; + if ( border ) { + size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0; + } + if ( margin ) { + size -= parseFloat( $.css( elem, "margin" + this ) ) || 0; + } + }); + return size; + } + + $.fn[ "inner" + name ] = function( size ) { + if ( size === undefined ) { + return orig[ "inner" + name ].call( this ); + } + + return this.each(function() { + $( this ).css( type, reduce( this, size ) + "px" ); + }); + }; + + $.fn[ "outer" + name] = function( size, margin ) { + if ( typeof size !== "number" ) { + return orig[ "outer" + name ].call( this, size ); + } + + return this.each(function() { + $( this).css( type, reduce( this, size, true, margin ) + "px" ); + }); + }; + }); +} + +// support: jQuery <1.8 +if ( !$.fn.addBack ) { + $.fn.addBack = function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + }; +} + +// support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413) +if ( $( "" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) { + $.fn.removeData = (function( removeData ) { + return function( key ) { + if ( arguments.length ) { + return removeData.call( this, $.camelCase( key ) ); + } else { + return removeData.call( this ); + } + }; + })( $.fn.removeData ); +} + +// deprecated +$.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ); + +$.fn.extend({ + focus: (function( orig ) { + return function( delay, fn ) { + return typeof delay === "number" ? + this.each(function() { + var elem = this; + setTimeout(function() { + $( elem ).focus(); + if ( fn ) { + fn.call( elem ); + } + }, delay ); + }) : + orig.apply( this, arguments ); + }; + })( $.fn.focus ), + + disableSelection: (function() { + var eventType = "onselectstart" in document.createElement( "div" ) ? + "selectstart" : + "mousedown"; + + return function() { + return this.bind( eventType + ".ui-disableSelection", function( event ) { + event.preventDefault(); + }); + }; + })(), + + enableSelection: function() { + return this.unbind( ".ui-disableSelection" ); + }, + + zIndex: function( zIndex ) { + if ( zIndex !== undefined ) { + return this.css( "zIndex", zIndex ); + } + + if ( this.length ) { + var elem = $( this[ 0 ] ), position, value; + while ( elem.length && elem[ 0 ] !== document ) { + // Ignore z-index if position is set to a value where z-index is ignored by the browser + // This makes behavior of this function consistent across browsers + // WebKit always returns auto if the element is positioned + position = elem.css( "position" ); + if ( position === "absolute" || position === "relative" || position === "fixed" ) { + // IE returns 0 when zIndex is not specified + // other browsers return a string + // we ignore the case of nested elements with an explicit value of 0 + //
              + value = parseInt( elem.css( "zIndex" ), 10 ); + if ( !isNaN( value ) && value !== 0 ) { + return value; + } + } + elem = elem.parent(); + } + } + + return 0; + } +}); + +// $.ui.plugin is deprecated. Use $.widget() extensions instead. +$.ui.plugin = { + add: function( module, option, set ) { + var i, + proto = $.ui[ module ].prototype; + for ( i in set ) { + proto.plugins[ i ] = proto.plugins[ i ] || []; + proto.plugins[ i ].push( [ option, set[ i ] ] ); + } + }, + call: function( instance, name, args, allowDisconnected ) { + var i, + set = instance.plugins[ name ]; + + if ( !set ) { + return; + } + + if ( !allowDisconnected && ( !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) ) { + return; + } + + for ( i = 0; i < set.length; i++ ) { + if ( instance.options[ set[ i ][ 0 ] ] ) { + set[ i ][ 1 ].apply( instance.element, args ); + } + } + } +}; + + +/*! + * jQuery UI Widget 1.11.2 + * http://jqueryui.com + * + * Copyright 2014 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/jQuery.widget/ + */ + + +var widget_uuid = 0, + widget_slice = Array.prototype.slice; + +$.cleanData = (function( orig ) { + return function( elems ) { + var events, elem, i; + for ( i = 0; (elem = elems[i]) != null; i++ ) { + try { + + // Only trigger remove when necessary to save time + events = $._data( elem, "events" ); + if ( events && events.remove ) { + $( elem ).triggerHandler( "remove" ); + } + + // http://bugs.jquery.com/ticket/8235 + } catch ( e ) {} + } + orig( elems ); + }; +})( $.cleanData ); + +$.widget = function( name, base, prototype ) { + var fullName, existingConstructor, constructor, basePrototype, + // proxiedPrototype allows the provided prototype to remain unmodified + // so that it can be used as a mixin for multiple widgets (#8876) + proxiedPrototype = {}, + namespace = name.split( "." )[ 0 ]; + + name = name.split( "." )[ 1 ]; + fullName = namespace + "-" + name; + + if ( !prototype ) { + prototype = base; + base = $.Widget; + } + + // create selector for plugin + $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { + return !!$.data( elem, fullName ); + }; + + $[ namespace ] = $[ namespace ] || {}; + existingConstructor = $[ namespace ][ name ]; + constructor = $[ namespace ][ name ] = function( options, element ) { + // allow instantiation without "new" keyword + if ( !this._createWidget ) { + return new constructor( options, element ); + } + + // allow instantiation without initializing for simple inheritance + // must use "new" keyword (the code above always passes args) + if ( arguments.length ) { + this._createWidget( options, element ); + } + }; + // extend with the existing constructor to carry over any static properties + $.extend( constructor, existingConstructor, { + version: prototype.version, + // copy the object used to create the prototype in case we need to + // redefine the widget later + _proto: $.extend( {}, prototype ), + // track widgets that inherit from this widget in case this widget is + // redefined after a widget inherits from it + _childConstructors: [] + }); + + basePrototype = new base(); + // we need to make the options hash a property directly on the new instance + // otherwise we'll modify the options hash on the prototype that we're + // inheriting from + basePrototype.options = $.widget.extend( {}, basePrototype.options ); + $.each( prototype, function( prop, value ) { + if ( !$.isFunction( value ) ) { + proxiedPrototype[ prop ] = value; + return; + } + proxiedPrototype[ prop ] = (function() { + var _super = function() { + return base.prototype[ prop ].apply( this, arguments ); + }, + _superApply = function( args ) { + return base.prototype[ prop ].apply( this, args ); + }; + return function() { + var __super = this._super, + __superApply = this._superApply, + returnValue; + + this._super = _super; + this._superApply = _superApply; + + returnValue = value.apply( this, arguments ); + + this._super = __super; + this._superApply = __superApply; + + return returnValue; + }; + })(); + }); + constructor.prototype = $.widget.extend( basePrototype, { + // TODO: remove support for widgetEventPrefix + // always use the name + a colon as the prefix, e.g., draggable:start + // don't prefix for widgets that aren't DOM-based + widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name + }, proxiedPrototype, { + constructor: constructor, + namespace: namespace, + widgetName: name, + widgetFullName: fullName + }); + + // If this widget is being redefined then we need to find all widgets that + // are inheriting from it and redefine all of them so that they inherit from + // the new version of this widget. We're essentially trying to replace one + // level in the prototype chain. + if ( existingConstructor ) { + $.each( existingConstructor._childConstructors, function( i, child ) { + var childPrototype = child.prototype; + + // redefine the child widget using the same prototype that was + // originally used, but inherit from the new version of the base + $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto ); + }); + // remove the list of existing child constructors from the old constructor + // so the old child constructors can be garbage collected + delete existingConstructor._childConstructors; + } else { + base._childConstructors.push( constructor ); + } + + $.widget.bridge( name, constructor ); + + return constructor; +}; + +$.widget.extend = function( target ) { + var input = widget_slice.call( arguments, 1 ), + inputIndex = 0, + inputLength = input.length, + key, + value; + for ( ; inputIndex < inputLength; inputIndex++ ) { + for ( key in input[ inputIndex ] ) { + value = input[ inputIndex ][ key ]; + if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { + // Clone objects + if ( $.isPlainObject( value ) ) { + target[ key ] = $.isPlainObject( target[ key ] ) ? + $.widget.extend( {}, target[ key ], value ) : + // Don't extend strings, arrays, etc. with objects + $.widget.extend( {}, value ); + // Copy everything else by reference + } else { + target[ key ] = value; + } + } + } + } + return target; +}; + +$.widget.bridge = function( name, object ) { + var fullName = object.prototype.widgetFullName || name; + $.fn[ name ] = function( options ) { + var isMethodCall = typeof options === "string", + args = widget_slice.call( arguments, 1 ), + returnValue = this; + + // allow multiple hashes to be passed on init + options = !isMethodCall && args.length ? + $.widget.extend.apply( null, [ options ].concat(args) ) : + options; + + if ( isMethodCall ) { + this.each(function() { + var methodValue, + instance = $.data( this, fullName ); + if ( options === "instance" ) { + returnValue = instance; + return false; + } + if ( !instance ) { + return $.error( "cannot call methods on " + name + " prior to initialization; " + + "attempted to call method '" + options + "'" ); + } + if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) { + return $.error( "no such method '" + options + "' for " + name + " widget instance" ); + } + methodValue = instance[ options ].apply( instance, args ); + if ( methodValue !== instance && methodValue !== undefined ) { + returnValue = methodValue && methodValue.jquery ? + returnValue.pushStack( methodValue.get() ) : + methodValue; + return false; + } + }); + } else { + this.each(function() { + var instance = $.data( this, fullName ); + if ( instance ) { + instance.option( options || {} ); + if ( instance._init ) { + instance._init(); + } + } else { + $.data( this, fullName, new object( options, this ) ); + } + }); + } + + return returnValue; + }; +}; + +$.Widget = function( /* options, element */ ) {}; +$.Widget._childConstructors = []; + +$.Widget.prototype = { + widgetName: "widget", + widgetEventPrefix: "", + defaultElement: "
              ", + options: { + disabled: false, + + // callbacks + create: null + }, + _createWidget: function( options, element ) { + element = $( element || this.defaultElement || this )[ 0 ]; + this.element = $( element ); + this.uuid = widget_uuid++; + this.eventNamespace = "." + this.widgetName + this.uuid; + + this.bindings = $(); + this.hoverable = $(); + this.focusable = $(); + + if ( element !== this ) { + $.data( element, this.widgetFullName, this ); + this._on( true, this.element, { + remove: function( event ) { + if ( event.target === element ) { + this.destroy(); + } + } + }); + this.document = $( element.style ? + // element within the document + element.ownerDocument : + // element is window or document + element.document || element ); + this.window = $( this.document[0].defaultView || this.document[0].parentWindow ); + } + + this.options = $.widget.extend( {}, + this.options, + this._getCreateOptions(), + options ); + + this._create(); + this._trigger( "create", null, this._getCreateEventData() ); + this._init(); + }, + _getCreateOptions: $.noop, + _getCreateEventData: $.noop, + _create: $.noop, + _init: $.noop, + + destroy: function() { + this._destroy(); + // we can probably remove the unbind calls in 2.0 + // all event bindings should go through this._on() + this.element + .unbind( this.eventNamespace ) + .removeData( this.widgetFullName ) + // support: jquery <1.6.3 + // http://bugs.jquery.com/ticket/9413 + .removeData( $.camelCase( this.widgetFullName ) ); + this.widget() + .unbind( this.eventNamespace ) + .removeAttr( "aria-disabled" ) + .removeClass( + this.widgetFullName + "-disabled " + + "ui-state-disabled" ); + + // clean up events and states + this.bindings.unbind( this.eventNamespace ); + this.hoverable.removeClass( "ui-state-hover" ); + this.focusable.removeClass( "ui-state-focus" ); + }, + _destroy: $.noop, + + widget: function() { + return this.element; + }, + + option: function( key, value ) { + var options = key, + parts, + curOption, + i; + + if ( arguments.length === 0 ) { + // don't return a reference to the internal hash + return $.widget.extend( {}, this.options ); + } + + if ( typeof key === "string" ) { + // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } + options = {}; + parts = key.split( "." ); + key = parts.shift(); + if ( parts.length ) { + curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); + for ( i = 0; i < parts.length - 1; i++ ) { + curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; + curOption = curOption[ parts[ i ] ]; + } + key = parts.pop(); + if ( arguments.length === 1 ) { + return curOption[ key ] === undefined ? null : curOption[ key ]; + } + curOption[ key ] = value; + } else { + if ( arguments.length === 1 ) { + return this.options[ key ] === undefined ? null : this.options[ key ]; + } + options[ key ] = value; + } + } + + this._setOptions( options ); + + return this; + }, + _setOptions: function( options ) { + var key; + + for ( key in options ) { + this._setOption( key, options[ key ] ); + } + + return this; + }, + _setOption: function( key, value ) { + this.options[ key ] = value; + + if ( key === "disabled" ) { + this.widget() + .toggleClass( this.widgetFullName + "-disabled", !!value ); + + // If the widget is becoming disabled, then nothing is interactive + if ( value ) { + this.hoverable.removeClass( "ui-state-hover" ); + this.focusable.removeClass( "ui-state-focus" ); + } + } + + return this; + }, + + enable: function() { + return this._setOptions({ disabled: false }); + }, + disable: function() { + return this._setOptions({ disabled: true }); + }, + + _on: function( suppressDisabledCheck, element, handlers ) { + var delegateElement, + instance = this; + + // no suppressDisabledCheck flag, shuffle arguments + if ( typeof suppressDisabledCheck !== "boolean" ) { + handlers = element; + element = suppressDisabledCheck; + suppressDisabledCheck = false; + } + + // no element argument, shuffle and use this.element + if ( !handlers ) { + handlers = element; + element = this.element; + delegateElement = this.widget(); + } else { + element = delegateElement = $( element ); + this.bindings = this.bindings.add( element ); + } + + $.each( handlers, function( event, handler ) { + function handlerProxy() { + // allow widgets to customize the disabled handling + // - disabled as an array instead of boolean + // - disabled class as method for disabling individual parts + if ( !suppressDisabledCheck && + ( instance.options.disabled === true || + $( this ).hasClass( "ui-state-disabled" ) ) ) { + return; + } + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + + // copy the guid so direct unbinding works + if ( typeof handler !== "string" ) { + handlerProxy.guid = handler.guid = + handler.guid || handlerProxy.guid || $.guid++; + } + + var match = event.match( /^([\w:-]*)\s*(.*)$/ ), + eventName = match[1] + instance.eventNamespace, + selector = match[2]; + if ( selector ) { + delegateElement.delegate( selector, eventName, handlerProxy ); + } else { + element.bind( eventName, handlerProxy ); + } + }); + }, + + _off: function( element, eventName ) { + eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + + this.eventNamespace; + element.unbind( eventName ).undelegate( eventName ); + + // Clear the stack to avoid memory leaks (#10056) + this.bindings = $( this.bindings.not( element ).get() ); + this.focusable = $( this.focusable.not( element ).get() ); + this.hoverable = $( this.hoverable.not( element ).get() ); + }, + + _delay: function( handler, delay ) { + function handlerProxy() { + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + var instance = this; + return setTimeout( handlerProxy, delay || 0 ); + }, + + _hoverable: function( element ) { + this.hoverable = this.hoverable.add( element ); + this._on( element, { + mouseenter: function( event ) { + $( event.currentTarget ).addClass( "ui-state-hover" ); + }, + mouseleave: function( event ) { + $( event.currentTarget ).removeClass( "ui-state-hover" ); + } + }); + }, + + _focusable: function( element ) { + this.focusable = this.focusable.add( element ); + this._on( element, { + focusin: function( event ) { + $( event.currentTarget ).addClass( "ui-state-focus" ); + }, + focusout: function( event ) { + $( event.currentTarget ).removeClass( "ui-state-focus" ); + } + }); + }, + + _trigger: function( type, event, data ) { + var prop, orig, + callback = this.options[ type ]; + + data = data || {}; + event = $.Event( event ); + event.type = ( type === this.widgetEventPrefix ? + type : + this.widgetEventPrefix + type ).toLowerCase(); + // the original event may come from any element + // so we need to reset the target on the new event + event.target = this.element[ 0 ]; + + // copy original event properties over to the new event + orig = event.originalEvent; + if ( orig ) { + for ( prop in orig ) { + if ( !( prop in event ) ) { + event[ prop ] = orig[ prop ]; + } + } + } + + this.element.trigger( event, data ); + return !( $.isFunction( callback ) && + callback.apply( this.element[0], [ event ].concat( data ) ) === false || + event.isDefaultPrevented() ); + } +}; + +$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { + $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { + if ( typeof options === "string" ) { + options = { effect: options }; + } + var hasOptions, + effectName = !options ? + method : + options === true || typeof options === "number" ? + defaultEffect : + options.effect || defaultEffect; + options = options || {}; + if ( typeof options === "number" ) { + options = { duration: options }; + } + hasOptions = !$.isEmptyObject( options ); + options.complete = callback; + if ( options.delay ) { + element.delay( options.delay ); + } + if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { + element[ method ]( options ); + } else if ( effectName !== method && element[ effectName ] ) { + element[ effectName ]( options.duration, options.easing, callback ); + } else { + element.queue(function( next ) { + $( this )[ method ](); + if ( callback ) { + callback.call( element[ 0 ] ); + } + next(); + }); + } + }; +}); + +var widget = $.widget; + + +/*! + * jQuery UI Mouse 1.11.2 + * http://jqueryui.com + * + * Copyright 2014 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/mouse/ + */ + + +var mouseHandled = false; +$( document ).mouseup( function() { + mouseHandled = false; +}); + +var mouse = $.widget("ui.mouse", { + version: "1.11.2", + options: { + cancel: "input,textarea,button,select,option", + distance: 1, + delay: 0 + }, + _mouseInit: function() { + var that = this; + + this.element + .bind("mousedown." + this.widgetName, function(event) { + return that._mouseDown(event); + }) + .bind("click." + this.widgetName, function(event) { + if (true === $.data(event.target, that.widgetName + ".preventClickEvent")) { + $.removeData(event.target, that.widgetName + ".preventClickEvent"); + event.stopImmediatePropagation(); + return false; + } + }); + + this.started = false; + }, + + // TODO: make sure destroying one instance of mouse doesn't mess with + // other instances of mouse + _mouseDestroy: function() { + this.element.unbind("." + this.widgetName); + if ( this._mouseMoveDelegate ) { + this.document + .unbind("mousemove." + this.widgetName, this._mouseMoveDelegate) + .unbind("mouseup." + this.widgetName, this._mouseUpDelegate); + } + }, + + _mouseDown: function(event) { + // don't let more than one widget handle mouseStart + if ( mouseHandled ) { + return; + } + + this._mouseMoved = false; + + // we may have missed mouseup (out of window) + (this._mouseStarted && this._mouseUp(event)); + + this._mouseDownEvent = event; + + var that = this, + btnIsLeft = (event.which === 1), + // event.target.nodeName works around a bug in IE 8 with + // disabled inputs (#7620) + elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false); + if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) { + return true; + } + + this.mouseDelayMet = !this.options.delay; + if (!this.mouseDelayMet) { + this._mouseDelayTimer = setTimeout(function() { + that.mouseDelayMet = true; + }, this.options.delay); + } + + if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { + this._mouseStarted = (this._mouseStart(event) !== false); + if (!this._mouseStarted) { + event.preventDefault(); + return true; + } + } + + // Click event may never have fired (Gecko & Opera) + if (true === $.data(event.target, this.widgetName + ".preventClickEvent")) { + $.removeData(event.target, this.widgetName + ".preventClickEvent"); + } + + // these delegates are required to keep context + this._mouseMoveDelegate = function(event) { + return that._mouseMove(event); + }; + this._mouseUpDelegate = function(event) { + return that._mouseUp(event); + }; + + this.document + .bind( "mousemove." + this.widgetName, this._mouseMoveDelegate ) + .bind( "mouseup." + this.widgetName, this._mouseUpDelegate ); + + event.preventDefault(); + + mouseHandled = true; + return true; + }, + + _mouseMove: function(event) { + // Only check for mouseups outside the document if you've moved inside the document + // at least once. This prevents the firing of mouseup in the case of IE<9, which will + // fire a mousemove event if content is placed under the cursor. See #7778 + // Support: IE <9 + if ( this._mouseMoved ) { + // IE mouseup check - mouseup happened when mouse was out of window + if ($.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button) { + return this._mouseUp(event); + + // Iframe mouseup check - mouseup occurred in another document + } else if ( !event.which ) { + return this._mouseUp( event ); + } + } + + if ( event.which || event.button ) { + this._mouseMoved = true; + } + + if (this._mouseStarted) { + this._mouseDrag(event); + return event.preventDefault(); + } + + if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { + this._mouseStarted = + (this._mouseStart(this._mouseDownEvent, event) !== false); + (this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event)); + } + + return !this._mouseStarted; + }, + + _mouseUp: function(event) { + this.document + .unbind( "mousemove." + this.widgetName, this._mouseMoveDelegate ) + .unbind( "mouseup." + this.widgetName, this._mouseUpDelegate ); + + if (this._mouseStarted) { + this._mouseStarted = false; + + if (event.target === this._mouseDownEvent.target) { + $.data(event.target, this.widgetName + ".preventClickEvent", true); + } + + this._mouseStop(event); + } + + mouseHandled = false; + return false; + }, + + _mouseDistanceMet: function(event) { + return (Math.max( + Math.abs(this._mouseDownEvent.pageX - event.pageX), + Math.abs(this._mouseDownEvent.pageY - event.pageY) + ) >= this.options.distance + ); + }, + + _mouseDelayMet: function(/* event */) { + return this.mouseDelayMet; + }, + + // These are placeholder methods, to be overriden by extending plugin + _mouseStart: function(/* event */) {}, + _mouseDrag: function(/* event */) {}, + _mouseStop: function(/* event */) {}, + _mouseCapture: function(/* event */) { return true; } +}); + + +/*! + * jQuery UI Position 1.11.2 + * http://jqueryui.com + * + * Copyright 2014 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/position/ + */ + +(function() { + +$.ui = $.ui || {}; + +var cachedScrollbarWidth, supportsOffsetFractions, + max = Math.max, + abs = Math.abs, + round = Math.round, + rhorizontal = /left|center|right/, + rvertical = /top|center|bottom/, + roffset = /[\+\-]\d+(\.[\d]+)?%?/, + rposition = /^\w+/, + rpercent = /%$/, + _position = $.fn.position; + +function getOffsets( offsets, width, height ) { + return [ + parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ), + parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 ) + ]; +} + +function parseCss( element, property ) { + return parseInt( $.css( element, property ), 10 ) || 0; +} + +function getDimensions( elem ) { + var raw = elem[0]; + if ( raw.nodeType === 9 ) { + return { + width: elem.width(), + height: elem.height(), + offset: { top: 0, left: 0 } + }; + } + if ( $.isWindow( raw ) ) { + return { + width: elem.width(), + height: elem.height(), + offset: { top: elem.scrollTop(), left: elem.scrollLeft() } + }; + } + if ( raw.preventDefault ) { + return { + width: 0, + height: 0, + offset: { top: raw.pageY, left: raw.pageX } + }; + } + return { + width: elem.outerWidth(), + height: elem.outerHeight(), + offset: elem.offset() + }; +} + +$.position = { + scrollbarWidth: function() { + if ( cachedScrollbarWidth !== undefined ) { + return cachedScrollbarWidth; + } + var w1, w2, + div = $( "
              " ), + innerDiv = div.children()[0]; + + $( "body" ).append( div ); + w1 = innerDiv.offsetWidth; + div.css( "overflow", "scroll" ); + + w2 = innerDiv.offsetWidth; + + if ( w1 === w2 ) { + w2 = div[0].clientWidth; + } + + div.remove(); + + return (cachedScrollbarWidth = w1 - w2); + }, + getScrollInfo: function( within ) { + var overflowX = within.isWindow || within.isDocument ? "" : + within.element.css( "overflow-x" ), + overflowY = within.isWindow || within.isDocument ? "" : + within.element.css( "overflow-y" ), + hasOverflowX = overflowX === "scroll" || + ( overflowX === "auto" && within.width < within.element[0].scrollWidth ), + hasOverflowY = overflowY === "scroll" || + ( overflowY === "auto" && within.height < within.element[0].scrollHeight ); + return { + width: hasOverflowY ? $.position.scrollbarWidth() : 0, + height: hasOverflowX ? $.position.scrollbarWidth() : 0 + }; + }, + getWithinInfo: function( element ) { + var withinElement = $( element || window ), + isWindow = $.isWindow( withinElement[0] ), + isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9; + return { + element: withinElement, + isWindow: isWindow, + isDocument: isDocument, + offset: withinElement.offset() || { left: 0, top: 0 }, + scrollLeft: withinElement.scrollLeft(), + scrollTop: withinElement.scrollTop(), + + // support: jQuery 1.6.x + // jQuery 1.6 doesn't support .outerWidth/Height() on documents or windows + width: isWindow || isDocument ? withinElement.width() : withinElement.outerWidth(), + height: isWindow || isDocument ? withinElement.height() : withinElement.outerHeight() + }; + } +}; + +$.fn.position = function( options ) { + if ( !options || !options.of ) { + return _position.apply( this, arguments ); + } + + // make a copy, we don't want to modify arguments + options = $.extend( {}, options ); + + var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions, + target = $( options.of ), + within = $.position.getWithinInfo( options.within ), + scrollInfo = $.position.getScrollInfo( within ), + collision = ( options.collision || "flip" ).split( " " ), + offsets = {}; + + dimensions = getDimensions( target ); + if ( target[0].preventDefault ) { + // force left top to allow flipping + options.at = "left top"; + } + targetWidth = dimensions.width; + targetHeight = dimensions.height; + targetOffset = dimensions.offset; + // clone to reuse original targetOffset later + basePosition = $.extend( {}, targetOffset ); + + // force my and at to have valid horizontal and vertical positions + // if a value is missing or invalid, it will be converted to center + $.each( [ "my", "at" ], function() { + var pos = ( options[ this ] || "" ).split( " " ), + horizontalOffset, + verticalOffset; + + if ( pos.length === 1) { + pos = rhorizontal.test( pos[ 0 ] ) ? + pos.concat( [ "center" ] ) : + rvertical.test( pos[ 0 ] ) ? + [ "center" ].concat( pos ) : + [ "center", "center" ]; + } + pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center"; + pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center"; + + // calculate offsets + horizontalOffset = roffset.exec( pos[ 0 ] ); + verticalOffset = roffset.exec( pos[ 1 ] ); + offsets[ this ] = [ + horizontalOffset ? horizontalOffset[ 0 ] : 0, + verticalOffset ? verticalOffset[ 0 ] : 0 + ]; + + // reduce to just the positions without the offsets + options[ this ] = [ + rposition.exec( pos[ 0 ] )[ 0 ], + rposition.exec( pos[ 1 ] )[ 0 ] + ]; + }); + + // normalize collision option + if ( collision.length === 1 ) { + collision[ 1 ] = collision[ 0 ]; + } + + if ( options.at[ 0 ] === "right" ) { + basePosition.left += targetWidth; + } else if ( options.at[ 0 ] === "center" ) { + basePosition.left += targetWidth / 2; + } + + if ( options.at[ 1 ] === "bottom" ) { + basePosition.top += targetHeight; + } else if ( options.at[ 1 ] === "center" ) { + basePosition.top += targetHeight / 2; + } + + atOffset = getOffsets( offsets.at, targetWidth, targetHeight ); + basePosition.left += atOffset[ 0 ]; + basePosition.top += atOffset[ 1 ]; + + return this.each(function() { + var collisionPosition, using, + elem = $( this ), + elemWidth = elem.outerWidth(), + elemHeight = elem.outerHeight(), + marginLeft = parseCss( this, "marginLeft" ), + marginTop = parseCss( this, "marginTop" ), + collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width, + collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height, + position = $.extend( {}, basePosition ), + myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() ); + + if ( options.my[ 0 ] === "right" ) { + position.left -= elemWidth; + } else if ( options.my[ 0 ] === "center" ) { + position.left -= elemWidth / 2; + } + + if ( options.my[ 1 ] === "bottom" ) { + position.top -= elemHeight; + } else if ( options.my[ 1 ] === "center" ) { + position.top -= elemHeight / 2; + } + + position.left += myOffset[ 0 ]; + position.top += myOffset[ 1 ]; + + // if the browser doesn't support fractions, then round for consistent results + if ( !supportsOffsetFractions ) { + position.left = round( position.left ); + position.top = round( position.top ); + } + + collisionPosition = { + marginLeft: marginLeft, + marginTop: marginTop + }; + + $.each( [ "left", "top" ], function( i, dir ) { + if ( $.ui.position[ collision[ i ] ] ) { + $.ui.position[ collision[ i ] ][ dir ]( position, { + targetWidth: targetWidth, + targetHeight: targetHeight, + elemWidth: elemWidth, + elemHeight: elemHeight, + collisionPosition: collisionPosition, + collisionWidth: collisionWidth, + collisionHeight: collisionHeight, + offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ], + my: options.my, + at: options.at, + within: within, + elem: elem + }); + } + }); + + if ( options.using ) { + // adds feedback as second argument to using callback, if present + using = function( props ) { + var left = targetOffset.left - position.left, + right = left + targetWidth - elemWidth, + top = targetOffset.top - position.top, + bottom = top + targetHeight - elemHeight, + feedback = { + target: { + element: target, + left: targetOffset.left, + top: targetOffset.top, + width: targetWidth, + height: targetHeight + }, + element: { + element: elem, + left: position.left, + top: position.top, + width: elemWidth, + height: elemHeight + }, + horizontal: right < 0 ? "left" : left > 0 ? "right" : "center", + vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle" + }; + if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) { + feedback.horizontal = "center"; + } + if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) { + feedback.vertical = "middle"; + } + if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) { + feedback.important = "horizontal"; + } else { + feedback.important = "vertical"; + } + options.using.call( this, props, feedback ); + }; + } + + elem.offset( $.extend( position, { using: using } ) ); + }); +}; + +$.ui.position = { + fit: { + left: function( position, data ) { + var within = data.within, + withinOffset = within.isWindow ? within.scrollLeft : within.offset.left, + outerWidth = within.width, + collisionPosLeft = position.left - data.collisionPosition.marginLeft, + overLeft = withinOffset - collisionPosLeft, + overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset, + newOverRight; + + // element is wider than within + if ( data.collisionWidth > outerWidth ) { + // element is initially over the left side of within + if ( overLeft > 0 && overRight <= 0 ) { + newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset; + position.left += overLeft - newOverRight; + // element is initially over right side of within + } else if ( overRight > 0 && overLeft <= 0 ) { + position.left = withinOffset; + // element is initially over both left and right sides of within + } else { + if ( overLeft > overRight ) { + position.left = withinOffset + outerWidth - data.collisionWidth; + } else { + position.left = withinOffset; + } + } + // too far left -> align with left edge + } else if ( overLeft > 0 ) { + position.left += overLeft; + // too far right -> align with right edge + } else if ( overRight > 0 ) { + position.left -= overRight; + // adjust based on position and margin + } else { + position.left = max( position.left - collisionPosLeft, position.left ); + } + }, + top: function( position, data ) { + var within = data.within, + withinOffset = within.isWindow ? within.scrollTop : within.offset.top, + outerHeight = data.within.height, + collisionPosTop = position.top - data.collisionPosition.marginTop, + overTop = withinOffset - collisionPosTop, + overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset, + newOverBottom; + + // element is taller than within + if ( data.collisionHeight > outerHeight ) { + // element is initially over the top of within + if ( overTop > 0 && overBottom <= 0 ) { + newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset; + position.top += overTop - newOverBottom; + // element is initially over bottom of within + } else if ( overBottom > 0 && overTop <= 0 ) { + position.top = withinOffset; + // element is initially over both top and bottom of within + } else { + if ( overTop > overBottom ) { + position.top = withinOffset + outerHeight - data.collisionHeight; + } else { + position.top = withinOffset; + } + } + // too far up -> align with top + } else if ( overTop > 0 ) { + position.top += overTop; + // too far down -> align with bottom edge + } else if ( overBottom > 0 ) { + position.top -= overBottom; + // adjust based on position and margin + } else { + position.top = max( position.top - collisionPosTop, position.top ); + } + } + }, + flip: { + left: function( position, data ) { + var within = data.within, + withinOffset = within.offset.left + within.scrollLeft, + outerWidth = within.width, + offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left, + collisionPosLeft = position.left - data.collisionPosition.marginLeft, + overLeft = collisionPosLeft - offsetLeft, + overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft, + myOffset = data.my[ 0 ] === "left" ? + -data.elemWidth : + data.my[ 0 ] === "right" ? + data.elemWidth : + 0, + atOffset = data.at[ 0 ] === "left" ? + data.targetWidth : + data.at[ 0 ] === "right" ? + -data.targetWidth : + 0, + offset = -2 * data.offset[ 0 ], + newOverRight, + newOverLeft; + + if ( overLeft < 0 ) { + newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset; + if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) { + position.left += myOffset + atOffset + offset; + } + } else if ( overRight > 0 ) { + newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft; + if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) { + position.left += myOffset + atOffset + offset; + } + } + }, + top: function( position, data ) { + var within = data.within, + withinOffset = within.offset.top + within.scrollTop, + outerHeight = within.height, + offsetTop = within.isWindow ? within.scrollTop : within.offset.top, + collisionPosTop = position.top - data.collisionPosition.marginTop, + overTop = collisionPosTop - offsetTop, + overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop, + top = data.my[ 1 ] === "top", + myOffset = top ? + -data.elemHeight : + data.my[ 1 ] === "bottom" ? + data.elemHeight : + 0, + atOffset = data.at[ 1 ] === "top" ? + data.targetHeight : + data.at[ 1 ] === "bottom" ? + -data.targetHeight : + 0, + offset = -2 * data.offset[ 1 ], + newOverTop, + newOverBottom; + if ( overTop < 0 ) { + newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset; + if ( ( position.top + myOffset + atOffset + offset) > overTop && ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) ) { + position.top += myOffset + atOffset + offset; + } + } else if ( overBottom > 0 ) { + newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop; + if ( ( position.top + myOffset + atOffset + offset) > overBottom && ( newOverTop > 0 || abs( newOverTop ) < overBottom ) ) { + position.top += myOffset + atOffset + offset; + } + } + } + }, + flipfit: { + left: function() { + $.ui.position.flip.left.apply( this, arguments ); + $.ui.position.fit.left.apply( this, arguments ); + }, + top: function() { + $.ui.position.flip.top.apply( this, arguments ); + $.ui.position.fit.top.apply( this, arguments ); + } + } +}; + +// fraction support test +(function() { + var testElement, testElementParent, testElementStyle, offsetLeft, i, + body = document.getElementsByTagName( "body" )[ 0 ], + div = document.createElement( "div" ); + + //Create a "fake body" for testing based on method used in jQuery.support + testElement = document.createElement( body ? "div" : "body" ); + testElementStyle = { + visibility: "hidden", + width: 0, + height: 0, + border: 0, + margin: 0, + background: "none" + }; + if ( body ) { + $.extend( testElementStyle, { + position: "absolute", + left: "-1000px", + top: "-1000px" + }); + } + for ( i in testElementStyle ) { + testElement.style[ i ] = testElementStyle[ i ]; + } + testElement.appendChild( div ); + testElementParent = body || document.documentElement; + testElementParent.insertBefore( testElement, testElementParent.firstChild ); + + div.style.cssText = "position: absolute; left: 10.7432222px;"; + + offsetLeft = $( div ).offset().left; + supportsOffsetFractions = offsetLeft > 10 && offsetLeft < 11; + + testElement.innerHTML = ""; + testElementParent.removeChild( testElement ); +})(); + +})(); + +var position = $.ui.position; + + +/*! + * jQuery UI Accordion 1.11.2 + * http://jqueryui.com + * + * Copyright 2014 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/accordion/ + */ + + +var accordion = $.widget( "ui.accordion", { + version: "1.11.2", + options: { + active: 0, + animate: {}, + collapsible: false, + event: "click", + header: "> li > :first-child,> :not(li):even", + heightStyle: "auto", + icons: { + activeHeader: "ui-icon-triangle-1-s", + header: "ui-icon-triangle-1-e" + }, + + // callbacks + activate: null, + beforeActivate: null + }, + + hideProps: { + borderTopWidth: "hide", + borderBottomWidth: "hide", + paddingTop: "hide", + paddingBottom: "hide", + height: "hide" + }, + + showProps: { + borderTopWidth: "show", + borderBottomWidth: "show", + paddingTop: "show", + paddingBottom: "show", + height: "show" + }, + + _create: function() { + var options = this.options; + this.prevShow = this.prevHide = $(); + this.element.addClass( "ui-accordion ui-widget ui-helper-reset" ) + // ARIA + .attr( "role", "tablist" ); + + // don't allow collapsible: false and active: false / null + if ( !options.collapsible && (options.active === false || options.active == null) ) { + options.active = 0; + } + + this._processPanels(); + // handle negative values + if ( options.active < 0 ) { + options.active += this.headers.length; + } + this._refresh(); + }, + + _getCreateEventData: function() { + return { + header: this.active, + panel: !this.active.length ? $() : this.active.next() + }; + }, + + _createIcons: function() { + var icons = this.options.icons; + if ( icons ) { + $( "" ) + .addClass( "ui-accordion-header-icon ui-icon " + icons.header ) + .prependTo( this.headers ); + this.active.children( ".ui-accordion-header-icon" ) + .removeClass( icons.header ) + .addClass( icons.activeHeader ); + this.headers.addClass( "ui-accordion-icons" ); + } + }, + + _destroyIcons: function() { + this.headers + .removeClass( "ui-accordion-icons" ) + .children( ".ui-accordion-header-icon" ) + .remove(); + }, + + _destroy: function() { + var contents; + + // clean up main element + this.element + .removeClass( "ui-accordion ui-widget ui-helper-reset" ) + .removeAttr( "role" ); + + // clean up headers + this.headers + .removeClass( "ui-accordion-header ui-accordion-header-active ui-state-default " + + "ui-corner-all ui-state-active ui-state-disabled ui-corner-top" ) + .removeAttr( "role" ) + .removeAttr( "aria-expanded" ) + .removeAttr( "aria-selected" ) + .removeAttr( "aria-controls" ) + .removeAttr( "tabIndex" ) + .removeUniqueId(); + + this._destroyIcons(); + + // clean up content panels + contents = this.headers.next() + .removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom " + + "ui-accordion-content ui-accordion-content-active ui-state-disabled" ) + .css( "display", "" ) + .removeAttr( "role" ) + .removeAttr( "aria-hidden" ) + .removeAttr( "aria-labelledby" ) + .removeUniqueId(); + + if ( this.options.heightStyle !== "content" ) { + contents.css( "height", "" ); + } + }, + + _setOption: function( key, value ) { + if ( key === "active" ) { + // _activate() will handle invalid values and update this.options + this._activate( value ); + return; + } + + if ( key === "event" ) { + if ( this.options.event ) { + this._off( this.headers, this.options.event ); + } + this._setupEvents( value ); + } + + this._super( key, value ); + + // setting collapsible: false while collapsed; open first panel + if ( key === "collapsible" && !value && this.options.active === false ) { + this._activate( 0 ); + } + + if ( key === "icons" ) { + this._destroyIcons(); + if ( value ) { + this._createIcons(); + } + } + + // #5332 - opacity doesn't cascade to positioned elements in IE + // so we need to add the disabled class to the headers and panels + if ( key === "disabled" ) { + this.element + .toggleClass( "ui-state-disabled", !!value ) + .attr( "aria-disabled", value ); + this.headers.add( this.headers.next() ) + .toggleClass( "ui-state-disabled", !!value ); + } + }, + + _keydown: function( event ) { + if ( event.altKey || event.ctrlKey ) { + return; + } + + var keyCode = $.ui.keyCode, + length = this.headers.length, + currentIndex = this.headers.index( event.target ), + toFocus = false; + + switch ( event.keyCode ) { + case keyCode.RIGHT: + case keyCode.DOWN: + toFocus = this.headers[ ( currentIndex + 1 ) % length ]; + break; + case keyCode.LEFT: + case keyCode.UP: + toFocus = this.headers[ ( currentIndex - 1 + length ) % length ]; + break; + case keyCode.SPACE: + case keyCode.ENTER: + this._eventHandler( event ); + break; + case keyCode.HOME: + toFocus = this.headers[ 0 ]; + break; + case keyCode.END: + toFocus = this.headers[ length - 1 ]; + break; + } + + if ( toFocus ) { + $( event.target ).attr( "tabIndex", -1 ); + $( toFocus ).attr( "tabIndex", 0 ); + toFocus.focus(); + event.preventDefault(); + } + }, + + _panelKeyDown: function( event ) { + if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) { + $( event.currentTarget ).prev().focus(); + } + }, + + refresh: function() { + var options = this.options; + this._processPanels(); + + // was collapsed or no panel + if ( ( options.active === false && options.collapsible === true ) || !this.headers.length ) { + options.active = false; + this.active = $(); + // active false only when collapsible is true + } else if ( options.active === false ) { + this._activate( 0 ); + // was active, but active panel is gone + } else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) { + // all remaining panel are disabled + if ( this.headers.length === this.headers.find(".ui-state-disabled").length ) { + options.active = false; + this.active = $(); + // activate previous panel + } else { + this._activate( Math.max( 0, options.active - 1 ) ); + } + // was active, active panel still exists + } else { + // make sure active index is correct + options.active = this.headers.index( this.active ); + } + + this._destroyIcons(); + + this._refresh(); + }, + + _processPanels: function() { + var prevHeaders = this.headers, + prevPanels = this.panels; + + this.headers = this.element.find( this.options.header ) + .addClass( "ui-accordion-header ui-state-default ui-corner-all" ); + + this.panels = this.headers.next() + .addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" ) + .filter( ":not(.ui-accordion-content-active)" ) + .hide(); + + // Avoid memory leaks (#10056) + if ( prevPanels ) { + this._off( prevHeaders.not( this.headers ) ); + this._off( prevPanels.not( this.panels ) ); + } + }, + + _refresh: function() { + var maxHeight, + options = this.options, + heightStyle = options.heightStyle, + parent = this.element.parent(); + + this.active = this._findActive( options.active ) + .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" ) + .removeClass( "ui-corner-all" ); + this.active.next() + .addClass( "ui-accordion-content-active" ) + .show(); + + this.headers + .attr( "role", "tab" ) + .each(function() { + var header = $( this ), + headerId = header.uniqueId().attr( "id" ), + panel = header.next(), + panelId = panel.uniqueId().attr( "id" ); + header.attr( "aria-controls", panelId ); + panel.attr( "aria-labelledby", headerId ); + }) + .next() + .attr( "role", "tabpanel" ); + + this.headers + .not( this.active ) + .attr({ + "aria-selected": "false", + "aria-expanded": "false", + tabIndex: -1 + }) + .next() + .attr({ + "aria-hidden": "true" + }) + .hide(); + + // make sure at least one header is in the tab order + if ( !this.active.length ) { + this.headers.eq( 0 ).attr( "tabIndex", 0 ); + } else { + this.active.attr({ + "aria-selected": "true", + "aria-expanded": "true", + tabIndex: 0 + }) + .next() + .attr({ + "aria-hidden": "false" + }); + } + + this._createIcons(); + + this._setupEvents( options.event ); + + if ( heightStyle === "fill" ) { + maxHeight = parent.height(); + this.element.siblings( ":visible" ).each(function() { + var elem = $( this ), + position = elem.css( "position" ); + + if ( position === "absolute" || position === "fixed" ) { + return; + } + maxHeight -= elem.outerHeight( true ); + }); + + this.headers.each(function() { + maxHeight -= $( this ).outerHeight( true ); + }); + + this.headers.next() + .each(function() { + $( this ).height( Math.max( 0, maxHeight - + $( this ).innerHeight() + $( this ).height() ) ); + }) + .css( "overflow", "auto" ); + } else if ( heightStyle === "auto" ) { + maxHeight = 0; + this.headers.next() + .each(function() { + maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() ); + }) + .height( maxHeight ); + } + }, + + _activate: function( index ) { + var active = this._findActive( index )[ 0 ]; + + // trying to activate the already active panel + if ( active === this.active[ 0 ] ) { + return; + } + + // trying to collapse, simulate a click on the currently active header + active = active || this.active[ 0 ]; + + this._eventHandler({ + target: active, + currentTarget: active, + preventDefault: $.noop + }); + }, + + _findActive: function( selector ) { + return typeof selector === "number" ? this.headers.eq( selector ) : $(); + }, + + _setupEvents: function( event ) { + var events = { + keydown: "_keydown" + }; + if ( event ) { + $.each( event.split( " " ), function( index, eventName ) { + events[ eventName ] = "_eventHandler"; + }); + } + + this._off( this.headers.add( this.headers.next() ) ); + this._on( this.headers, events ); + this._on( this.headers.next(), { keydown: "_panelKeyDown" }); + this._hoverable( this.headers ); + this._focusable( this.headers ); + }, + + _eventHandler: function( event ) { + var options = this.options, + active = this.active, + clicked = $( event.currentTarget ), + clickedIsActive = clicked[ 0 ] === active[ 0 ], + collapsing = clickedIsActive && options.collapsible, + toShow = collapsing ? $() : clicked.next(), + toHide = active.next(), + eventData = { + oldHeader: active, + oldPanel: toHide, + newHeader: collapsing ? $() : clicked, + newPanel: toShow + }; + + event.preventDefault(); + + if ( + // click on active header, but not collapsible + ( clickedIsActive && !options.collapsible ) || + // allow canceling activation + ( this._trigger( "beforeActivate", event, eventData ) === false ) ) { + return; + } + + options.active = collapsing ? false : this.headers.index( clicked ); + + // when the call to ._toggle() comes after the class changes + // it causes a very odd bug in IE 8 (see #6720) + this.active = clickedIsActive ? $() : clicked; + this._toggle( eventData ); + + // switch classes + // corner classes on the previously active header stay after the animation + active.removeClass( "ui-accordion-header-active ui-state-active" ); + if ( options.icons ) { + active.children( ".ui-accordion-header-icon" ) + .removeClass( options.icons.activeHeader ) + .addClass( options.icons.header ); + } + + if ( !clickedIsActive ) { + clicked + .removeClass( "ui-corner-all" ) + .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" ); + if ( options.icons ) { + clicked.children( ".ui-accordion-header-icon" ) + .removeClass( options.icons.header ) + .addClass( options.icons.activeHeader ); + } + + clicked + .next() + .addClass( "ui-accordion-content-active" ); + } + }, + + _toggle: function( data ) { + var toShow = data.newPanel, + toHide = this.prevShow.length ? this.prevShow : data.oldPanel; + + // handle activating a panel during the animation for another activation + this.prevShow.add( this.prevHide ).stop( true, true ); + this.prevShow = toShow; + this.prevHide = toHide; + + if ( this.options.animate ) { + this._animate( toShow, toHide, data ); + } else { + toHide.hide(); + toShow.show(); + this._toggleComplete( data ); + } + + toHide.attr({ + "aria-hidden": "true" + }); + toHide.prev().attr( "aria-selected", "false" ); + // if we're switching panels, remove the old header from the tab order + // if we're opening from collapsed state, remove the previous header from the tab order + // if we're collapsing, then keep the collapsing header in the tab order + if ( toShow.length && toHide.length ) { + toHide.prev().attr({ + "tabIndex": -1, + "aria-expanded": "false" + }); + } else if ( toShow.length ) { + this.headers.filter(function() { + return $( this ).attr( "tabIndex" ) === 0; + }) + .attr( "tabIndex", -1 ); + } + + toShow + .attr( "aria-hidden", "false" ) + .prev() + .attr({ + "aria-selected": "true", + tabIndex: 0, + "aria-expanded": "true" + }); + }, + + _animate: function( toShow, toHide, data ) { + var total, easing, duration, + that = this, + adjust = 0, + down = toShow.length && + ( !toHide.length || ( toShow.index() < toHide.index() ) ), + animate = this.options.animate || {}, + options = down && animate.down || animate, + complete = function() { + that._toggleComplete( data ); + }; + + if ( typeof options === "number" ) { + duration = options; + } + if ( typeof options === "string" ) { + easing = options; + } + // fall back from options to animation in case of partial down settings + easing = easing || options.easing || animate.easing; + duration = duration || options.duration || animate.duration; + + if ( !toHide.length ) { + return toShow.animate( this.showProps, duration, easing, complete ); + } + if ( !toShow.length ) { + return toHide.animate( this.hideProps, duration, easing, complete ); + } + + total = toShow.show().outerHeight(); + toHide.animate( this.hideProps, { + duration: duration, + easing: easing, + step: function( now, fx ) { + fx.now = Math.round( now ); + } + }); + toShow + .hide() + .animate( this.showProps, { + duration: duration, + easing: easing, + complete: complete, + step: function( now, fx ) { + fx.now = Math.round( now ); + if ( fx.prop !== "height" ) { + adjust += fx.now; + } else if ( that.options.heightStyle !== "content" ) { + fx.now = Math.round( total - toHide.outerHeight() - adjust ); + adjust = 0; + } + } + }); + }, + + _toggleComplete: function( data ) { + var toHide = data.oldPanel; + + toHide + .removeClass( "ui-accordion-content-active" ) + .prev() + .removeClass( "ui-corner-top" ) + .addClass( "ui-corner-all" ); + + // Work around for rendering bug in IE (#5421) + if ( toHide.length ) { + toHide.parent()[ 0 ].className = toHide.parent()[ 0 ].className; + } + this._trigger( "activate", null, data ); + } +}); + + +/*! + * jQuery UI Menu 1.11.2 + * http://jqueryui.com + * + * Copyright 2014 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/menu/ + */ + + +var menu = $.widget( "ui.menu", { + version: "1.11.2", + defaultElement: "
                ", + delay: 300, + options: { + icons: { + submenu: "ui-icon-carat-1-e" + }, + items: "> *", + menus: "ul", + position: { + my: "left-1 top", + at: "right top" + }, + role: "menu", + + // callbacks + blur: null, + focus: null, + select: null + }, + + _create: function() { + this.activeMenu = this.element; + + // Flag used to prevent firing of the click handler + // as the event bubbles up through nested menus + this.mouseHandled = false; + this.element + .uniqueId() + .addClass( "ui-menu ui-widget ui-widget-content" ) + .toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length ) + .attr({ + role: this.options.role, + tabIndex: 0 + }); + + if ( this.options.disabled ) { + this.element + .addClass( "ui-state-disabled" ) + .attr( "aria-disabled", "true" ); + } + + this._on({ + // Prevent focus from sticking to links inside menu after clicking + // them (focus should always stay on UL during navigation). + "mousedown .ui-menu-item": function( event ) { + event.preventDefault(); + }, + "click .ui-menu-item": function( event ) { + var target = $( event.target ); + if ( !this.mouseHandled && target.not( ".ui-state-disabled" ).length ) { + this.select( event ); + + // Only set the mouseHandled flag if the event will bubble, see #9469. + if ( !event.isPropagationStopped() ) { + this.mouseHandled = true; + } + + // Open submenu on click + if ( target.has( ".ui-menu" ).length ) { + this.expand( event ); + } else if ( !this.element.is( ":focus" ) && $( this.document[ 0 ].activeElement ).closest( ".ui-menu" ).length ) { + + // Redirect focus to the menu + this.element.trigger( "focus", [ true ] ); + + // If the active item is on the top level, let it stay active. + // Otherwise, blur the active item since it is no longer visible. + if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) { + clearTimeout( this.timer ); + } + } + } + }, + "mouseenter .ui-menu-item": function( event ) { + // Ignore mouse events while typeahead is active, see #10458. + // Prevents focusing the wrong item when typeahead causes a scroll while the mouse + // is over an item in the menu + if ( this.previousFilter ) { + return; + } + var target = $( event.currentTarget ); + // Remove ui-state-active class from siblings of the newly focused menu item + // to avoid a jump caused by adjacent elements both having a class with a border + target.siblings( ".ui-state-active" ).removeClass( "ui-state-active" ); + this.focus( event, target ); + }, + mouseleave: "collapseAll", + "mouseleave .ui-menu": "collapseAll", + focus: function( event, keepActiveItem ) { + // If there's already an active item, keep it active + // If not, activate the first item + var item = this.active || this.element.find( this.options.items ).eq( 0 ); + + if ( !keepActiveItem ) { + this.focus( event, item ); + } + }, + blur: function( event ) { + this._delay(function() { + if ( !$.contains( this.element[0], this.document[0].activeElement ) ) { + this.collapseAll( event ); + } + }); + }, + keydown: "_keydown" + }); + + this.refresh(); + + // Clicks outside of a menu collapse any open menus + this._on( this.document, { + click: function( event ) { + if ( this._closeOnDocumentClick( event ) ) { + this.collapseAll( event ); + } + + // Reset the mouseHandled flag + this.mouseHandled = false; + } + }); + }, + + _destroy: function() { + // Destroy (sub)menus + this.element + .removeAttr( "aria-activedescendant" ) + .find( ".ui-menu" ).addBack() + .removeClass( "ui-menu ui-widget ui-widget-content ui-menu-icons ui-front" ) + .removeAttr( "role" ) + .removeAttr( "tabIndex" ) + .removeAttr( "aria-labelledby" ) + .removeAttr( "aria-expanded" ) + .removeAttr( "aria-hidden" ) + .removeAttr( "aria-disabled" ) + .removeUniqueId() + .show(); + + // Destroy menu items + this.element.find( ".ui-menu-item" ) + .removeClass( "ui-menu-item" ) + .removeAttr( "role" ) + .removeAttr( "aria-disabled" ) + .removeUniqueId() + .removeClass( "ui-state-hover" ) + .removeAttr( "tabIndex" ) + .removeAttr( "role" ) + .removeAttr( "aria-haspopup" ) + .children().each( function() { + var elem = $( this ); + if ( elem.data( "ui-menu-submenu-carat" ) ) { + elem.remove(); + } + }); + + // Destroy menu dividers + this.element.find( ".ui-menu-divider" ).removeClass( "ui-menu-divider ui-widget-content" ); + }, + + _keydown: function( event ) { + var match, prev, character, skip, + preventDefault = true; + + switch ( event.keyCode ) { + case $.ui.keyCode.PAGE_UP: + this.previousPage( event ); + break; + case $.ui.keyCode.PAGE_DOWN: + this.nextPage( event ); + break; + case $.ui.keyCode.HOME: + this._move( "first", "first", event ); + break; + case $.ui.keyCode.END: + this._move( "last", "last", event ); + break; + case $.ui.keyCode.UP: + this.previous( event ); + break; + case $.ui.keyCode.DOWN: + this.next( event ); + break; + case $.ui.keyCode.LEFT: + this.collapse( event ); + break; + case $.ui.keyCode.RIGHT: + if ( this.active && !this.active.is( ".ui-state-disabled" ) ) { + this.expand( event ); + } + break; + case $.ui.keyCode.ENTER: + case $.ui.keyCode.SPACE: + this._activate( event ); + break; + case $.ui.keyCode.ESCAPE: + this.collapse( event ); + break; + default: + preventDefault = false; + prev = this.previousFilter || ""; + character = String.fromCharCode( event.keyCode ); + skip = false; + + clearTimeout( this.filterTimer ); + + if ( character === prev ) { + skip = true; + } else { + character = prev + character; + } + + match = this._filterMenuItems( character ); + match = skip && match.index( this.active.next() ) !== -1 ? + this.active.nextAll( ".ui-menu-item" ) : + match; + + // If no matches on the current filter, reset to the last character pressed + // to move down the menu to the first item that starts with that character + if ( !match.length ) { + character = String.fromCharCode( event.keyCode ); + match = this._filterMenuItems( character ); + } + + if ( match.length ) { + this.focus( event, match ); + this.previousFilter = character; + this.filterTimer = this._delay(function() { + delete this.previousFilter; + }, 1000 ); + } else { + delete this.previousFilter; + } + } + + if ( preventDefault ) { + event.preventDefault(); + } + }, + + _activate: function( event ) { + if ( !this.active.is( ".ui-state-disabled" ) ) { + if ( this.active.is( "[aria-haspopup='true']" ) ) { + this.expand( event ); + } else { + this.select( event ); + } + } + }, + + refresh: function() { + var menus, items, + that = this, + icon = this.options.icons.submenu, + submenus = this.element.find( this.options.menus ); + + this.element.toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length ); + + // Initialize nested menus + submenus.filter( ":not(.ui-menu)" ) + .addClass( "ui-menu ui-widget ui-widget-content ui-front" ) + .hide() + .attr({ + role: this.options.role, + "aria-hidden": "true", + "aria-expanded": "false" + }) + .each(function() { + var menu = $( this ), + item = menu.parent(), + submenuCarat = $( "" ) + .addClass( "ui-menu-icon ui-icon " + icon ) + .data( "ui-menu-submenu-carat", true ); + + item + .attr( "aria-haspopup", "true" ) + .prepend( submenuCarat ); + menu.attr( "aria-labelledby", item.attr( "id" ) ); + }); + + menus = submenus.add( this.element ); + items = menus.find( this.options.items ); + + // Initialize menu-items containing spaces and/or dashes only as dividers + items.not( ".ui-menu-item" ).each(function() { + var item = $( this ); + if ( that._isDivider( item ) ) { + item.addClass( "ui-widget-content ui-menu-divider" ); + } + }); + + // Don't refresh list items that are already adapted + items.not( ".ui-menu-item, .ui-menu-divider" ) + .addClass( "ui-menu-item" ) + .uniqueId() + .attr({ + tabIndex: -1, + role: this._itemRole() + }); + + // Add aria-disabled attribute to any disabled menu item + items.filter( ".ui-state-disabled" ).attr( "aria-disabled", "true" ); + + // If the active item has been removed, blur the menu + if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) { + this.blur(); + } + }, + + _itemRole: function() { + return { + menu: "menuitem", + listbox: "option" + }[ this.options.role ]; + }, + + _setOption: function( key, value ) { + if ( key === "icons" ) { + this.element.find( ".ui-menu-icon" ) + .removeClass( this.options.icons.submenu ) + .addClass( value.submenu ); + } + if ( key === "disabled" ) { + this.element + .toggleClass( "ui-state-disabled", !!value ) + .attr( "aria-disabled", value ); + } + this._super( key, value ); + }, + + focus: function( event, item ) { + var nested, focused; + this.blur( event, event && event.type === "focus" ); + + this._scrollIntoView( item ); + + this.active = item.first(); + focused = this.active.addClass( "ui-state-focus" ).removeClass( "ui-state-active" ); + // Only update aria-activedescendant if there's a role + // otherwise we assume focus is managed elsewhere + if ( this.options.role ) { + this.element.attr( "aria-activedescendant", focused.attr( "id" ) ); + } + + // Highlight active parent menu item, if any + this.active + .parent() + .closest( ".ui-menu-item" ) + .addClass( "ui-state-active" ); + + if ( event && event.type === "keydown" ) { + this._close(); + } else { + this.timer = this._delay(function() { + this._close(); + }, this.delay ); + } + + nested = item.children( ".ui-menu" ); + if ( nested.length && event && ( /^mouse/.test( event.type ) ) ) { + this._startOpening(nested); + } + this.activeMenu = item.parent(); + + this._trigger( "focus", event, { item: item } ); + }, + + _scrollIntoView: function( item ) { + var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight; + if ( this._hasScroll() ) { + borderTop = parseFloat( $.css( this.activeMenu[0], "borderTopWidth" ) ) || 0; + paddingTop = parseFloat( $.css( this.activeMenu[0], "paddingTop" ) ) || 0; + offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop; + scroll = this.activeMenu.scrollTop(); + elementHeight = this.activeMenu.height(); + itemHeight = item.outerHeight(); + + if ( offset < 0 ) { + this.activeMenu.scrollTop( scroll + offset ); + } else if ( offset + itemHeight > elementHeight ) { + this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight ); + } + } + }, + + blur: function( event, fromFocus ) { + if ( !fromFocus ) { + clearTimeout( this.timer ); + } + + if ( !this.active ) { + return; + } + + this.active.removeClass( "ui-state-focus" ); + this.active = null; + + this._trigger( "blur", event, { item: this.active } ); + }, + + _startOpening: function( submenu ) { + clearTimeout( this.timer ); + + // Don't open if already open fixes a Firefox bug that caused a .5 pixel + // shift in the submenu position when mousing over the carat icon + if ( submenu.attr( "aria-hidden" ) !== "true" ) { + return; + } + + this.timer = this._delay(function() { + this._close(); + this._open( submenu ); + }, this.delay ); + }, + + _open: function( submenu ) { + var position = $.extend({ + of: this.active + }, this.options.position ); + + clearTimeout( this.timer ); + this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) ) + .hide() + .attr( "aria-hidden", "true" ); + + submenu + .show() + .removeAttr( "aria-hidden" ) + .attr( "aria-expanded", "true" ) + .position( position ); + }, + + collapseAll: function( event, all ) { + clearTimeout( this.timer ); + this.timer = this._delay(function() { + // If we were passed an event, look for the submenu that contains the event + var currentMenu = all ? this.element : + $( event && event.target ).closest( this.element.find( ".ui-menu" ) ); + + // If we found no valid submenu ancestor, use the main menu to close all sub menus anyway + if ( !currentMenu.length ) { + currentMenu = this.element; + } + + this._close( currentMenu ); + + this.blur( event ); + this.activeMenu = currentMenu; + }, this.delay ); + }, + + // With no arguments, closes the currently active menu - if nothing is active + // it closes all menus. If passed an argument, it will search for menus BELOW + _close: function( startMenu ) { + if ( !startMenu ) { + startMenu = this.active ? this.active.parent() : this.element; + } + + startMenu + .find( ".ui-menu" ) + .hide() + .attr( "aria-hidden", "true" ) + .attr( "aria-expanded", "false" ) + .end() + .find( ".ui-state-active" ).not( ".ui-state-focus" ) + .removeClass( "ui-state-active" ); + }, + + _closeOnDocumentClick: function( event ) { + return !$( event.target ).closest( ".ui-menu" ).length; + }, + + _isDivider: function( item ) { + + // Match hyphen, em dash, en dash + return !/[^\-\u2014\u2013\s]/.test( item.text() ); + }, + + collapse: function( event ) { + var newItem = this.active && + this.active.parent().closest( ".ui-menu-item", this.element ); + if ( newItem && newItem.length ) { + this._close(); + this.focus( event, newItem ); + } + }, + + expand: function( event ) { + var newItem = this.active && + this.active + .children( ".ui-menu " ) + .find( this.options.items ) + .first(); + + if ( newItem && newItem.length ) { + this._open( newItem.parent() ); + + // Delay so Firefox will not hide activedescendant change in expanding submenu from AT + this._delay(function() { + this.focus( event, newItem ); + }); + } + }, + + next: function( event ) { + this._move( "next", "first", event ); + }, + + previous: function( event ) { + this._move( "prev", "last", event ); + }, + + isFirstItem: function() { + return this.active && !this.active.prevAll( ".ui-menu-item" ).length; + }, + + isLastItem: function() { + return this.active && !this.active.nextAll( ".ui-menu-item" ).length; + }, + + _move: function( direction, filter, event ) { + var next; + if ( this.active ) { + if ( direction === "first" || direction === "last" ) { + next = this.active + [ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" ) + .eq( -1 ); + } else { + next = this.active + [ direction + "All" ]( ".ui-menu-item" ) + .eq( 0 ); + } + } + if ( !next || !next.length || !this.active ) { + next = this.activeMenu.find( this.options.items )[ filter ](); + } + + this.focus( event, next ); + }, + + nextPage: function( event ) { + var item, base, height; + + if ( !this.active ) { + this.next( event ); + return; + } + if ( this.isLastItem() ) { + return; + } + if ( this._hasScroll() ) { + base = this.active.offset().top; + height = this.element.height(); + this.active.nextAll( ".ui-menu-item" ).each(function() { + item = $( this ); + return item.offset().top - base - height < 0; + }); + + this.focus( event, item ); + } else { + this.focus( event, this.activeMenu.find( this.options.items ) + [ !this.active ? "first" : "last" ]() ); + } + }, + + previousPage: function( event ) { + var item, base, height; + if ( !this.active ) { + this.next( event ); + return; + } + if ( this.isFirstItem() ) { + return; + } + if ( this._hasScroll() ) { + base = this.active.offset().top; + height = this.element.height(); + this.active.prevAll( ".ui-menu-item" ).each(function() { + item = $( this ); + return item.offset().top - base + height > 0; + }); + + this.focus( event, item ); + } else { + this.focus( event, this.activeMenu.find( this.options.items ).first() ); + } + }, + + _hasScroll: function() { + return this.element.outerHeight() < this.element.prop( "scrollHeight" ); + }, + + select: function( event ) { + // TODO: It should never be possible to not have an active item at this + // point, but the tests don't trigger mouseenter before click. + this.active = this.active || $( event.target ).closest( ".ui-menu-item" ); + var ui = { item: this.active }; + if ( !this.active.has( ".ui-menu" ).length ) { + this.collapseAll( event, true ); + } + this._trigger( "select", event, ui ); + }, + + _filterMenuItems: function(character) { + var escapedCharacter = character.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ), + regex = new RegExp( "^" + escapedCharacter, "i" ); + + return this.activeMenu + .find( this.options.items ) + + // Only match on items, not dividers or other content (#10571) + .filter( ".ui-menu-item" ) + .filter(function() { + return regex.test( $.trim( $( this ).text() ) ); + }); + } +}); + + +/*! + * jQuery UI Autocomplete 1.11.2 + * http://jqueryui.com + * + * Copyright 2014 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/autocomplete/ + */ + + +$.widget( "ui.autocomplete", { + version: "1.11.2", + defaultElement: "", + options: { + appendTo: null, + autoFocus: false, + delay: 300, + minLength: 1, + position: { + my: "left top", + at: "left bottom", + collision: "none" + }, + source: null, + + // callbacks + change: null, + close: null, + focus: null, + open: null, + response: null, + search: null, + select: null + }, + + requestIndex: 0, + pending: 0, + + _create: function() { + // Some browsers only repeat keydown events, not keypress events, + // so we use the suppressKeyPress flag to determine if we've already + // handled the keydown event. #7269 + // Unfortunately the code for & in keypress is the same as the up arrow, + // so we use the suppressKeyPressRepeat flag to avoid handling keypress + // events when we know the keydown event was used to modify the + // search term. #7799 + var suppressKeyPress, suppressKeyPressRepeat, suppressInput, + nodeName = this.element[ 0 ].nodeName.toLowerCase(), + isTextarea = nodeName === "textarea", + isInput = nodeName === "input"; + + this.isMultiLine = + // Textareas are always multi-line + isTextarea ? true : + // Inputs are always single-line, even if inside a contentEditable element + // IE also treats inputs as contentEditable + isInput ? false : + // All other element types are determined by whether or not they're contentEditable + this.element.prop( "isContentEditable" ); + + this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ]; + this.isNewMenu = true; + + this.element + .addClass( "ui-autocomplete-input" ) + .attr( "autocomplete", "off" ); + + this._on( this.element, { + keydown: function( event ) { + if ( this.element.prop( "readOnly" ) ) { + suppressKeyPress = true; + suppressInput = true; + suppressKeyPressRepeat = true; + return; + } + + suppressKeyPress = false; + suppressInput = false; + suppressKeyPressRepeat = false; + var keyCode = $.ui.keyCode; + switch ( event.keyCode ) { + case keyCode.PAGE_UP: + suppressKeyPress = true; + this._move( "previousPage", event ); + break; + case keyCode.PAGE_DOWN: + suppressKeyPress = true; + this._move( "nextPage", event ); + break; + case keyCode.UP: + suppressKeyPress = true; + this._keyEvent( "previous", event ); + break; + case keyCode.DOWN: + suppressKeyPress = true; + this._keyEvent( "next", event ); + break; + case keyCode.ENTER: + // when menu is open and has focus + if ( this.menu.active ) { + // #6055 - Opera still allows the keypress to occur + // which causes forms to submit + suppressKeyPress = true; + event.preventDefault(); + this.menu.select( event ); + } + break; + case keyCode.TAB: + if ( this.menu.active ) { + this.menu.select( event ); + } + break; + case keyCode.ESCAPE: + if ( this.menu.element.is( ":visible" ) ) { + if ( !this.isMultiLine ) { + this._value( this.term ); + } + this.close( event ); + // Different browsers have different default behavior for escape + // Single press can mean undo or clear + // Double press in IE means clear the whole form + event.preventDefault(); + } + break; + default: + suppressKeyPressRepeat = true; + // search timeout should be triggered before the input value is changed + this._searchTimeout( event ); + break; + } + }, + keypress: function( event ) { + if ( suppressKeyPress ) { + suppressKeyPress = false; + if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { + event.preventDefault(); + } + return; + } + if ( suppressKeyPressRepeat ) { + return; + } + + // replicate some key handlers to allow them to repeat in Firefox and Opera + var keyCode = $.ui.keyCode; + switch ( event.keyCode ) { + case keyCode.PAGE_UP: + this._move( "previousPage", event ); + break; + case keyCode.PAGE_DOWN: + this._move( "nextPage", event ); + break; + case keyCode.UP: + this._keyEvent( "previous", event ); + break; + case keyCode.DOWN: + this._keyEvent( "next", event ); + break; + } + }, + input: function( event ) { + if ( suppressInput ) { + suppressInput = false; + event.preventDefault(); + return; + } + this._searchTimeout( event ); + }, + focus: function() { + this.selectedItem = null; + this.previous = this._value(); + }, + blur: function( event ) { + if ( this.cancelBlur ) { + delete this.cancelBlur; + return; + } + + clearTimeout( this.searching ); + this.close( event ); + this._change( event ); + } + }); + + this._initSource(); + this.menu = $( "
                  " ) + .addClass( "ui-autocomplete ui-front" ) + .appendTo( this._appendTo() ) + .menu({ + // disable ARIA support, the live region takes care of that + role: null + }) + .hide() + .menu( "instance" ); + + this._on( this.menu.element, { + mousedown: function( event ) { + // prevent moving focus out of the text field + event.preventDefault(); + + // IE doesn't prevent moving focus even with event.preventDefault() + // so we set a flag to know when we should ignore the blur event + this.cancelBlur = true; + this._delay(function() { + delete this.cancelBlur; + }); + + // clicking on the scrollbar causes focus to shift to the body + // but we can't detect a mouseup or a click immediately afterward + // so we have to track the next mousedown and close the menu if + // the user clicks somewhere outside of the autocomplete + var menuElement = this.menu.element[ 0 ]; + if ( !$( event.target ).closest( ".ui-menu-item" ).length ) { + this._delay(function() { + var that = this; + this.document.one( "mousedown", function( event ) { + if ( event.target !== that.element[ 0 ] && + event.target !== menuElement && + !$.contains( menuElement, event.target ) ) { + that.close(); + } + }); + }); + } + }, + menufocus: function( event, ui ) { + var label, item; + // support: Firefox + // Prevent accidental activation of menu items in Firefox (#7024 #9118) + if ( this.isNewMenu ) { + this.isNewMenu = false; + if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) { + this.menu.blur(); + + this.document.one( "mousemove", function() { + $( event.target ).trigger( event.originalEvent ); + }); + + return; + } + } + + item = ui.item.data( "ui-autocomplete-item" ); + if ( false !== this._trigger( "focus", event, { item: item } ) ) { + // use value to match what will end up in the input, if it was a key event + if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) { + this._value( item.value ); + } + } + + // Announce the value in the liveRegion + label = ui.item.attr( "aria-label" ) || item.value; + if ( label && $.trim( label ).length ) { + this.liveRegion.children().hide(); + $( "
                  " ).text( label ).appendTo( this.liveRegion ); + } + }, + menuselect: function( event, ui ) { + var item = ui.item.data( "ui-autocomplete-item" ), + previous = this.previous; + + // only trigger when focus was lost (click on menu) + if ( this.element[ 0 ] !== this.document[ 0 ].activeElement ) { + this.element.focus(); + this.previous = previous; + // #6109 - IE triggers two focus events and the second + // is asynchronous, so we need to reset the previous + // term synchronously and asynchronously :-( + this._delay(function() { + this.previous = previous; + this.selectedItem = item; + }); + } + + if ( false !== this._trigger( "select", event, { item: item } ) ) { + this._value( item.value ); + } + // reset the term after the select event + // this allows custom select handling to work properly + this.term = this._value(); + + this.close( event ); + this.selectedItem = item; + } + }); + + this.liveRegion = $( "", { + role: "status", + "aria-live": "assertive", + "aria-relevant": "additions" + }) + .addClass( "ui-helper-hidden-accessible" ) + .appendTo( this.document[ 0 ].body ); + + // turning off autocomplete prevents the browser from remembering the + // value when navigating through history, so we re-enable autocomplete + // if the page is unloaded before the widget is destroyed. #7790 + this._on( this.window, { + beforeunload: function() { + this.element.removeAttr( "autocomplete" ); + } + }); + }, + + _destroy: function() { + clearTimeout( this.searching ); + this.element + .removeClass( "ui-autocomplete-input" ) + .removeAttr( "autocomplete" ); + this.menu.element.remove(); + this.liveRegion.remove(); + }, + + _setOption: function( key, value ) { + this._super( key, value ); + if ( key === "source" ) { + this._initSource(); + } + if ( key === "appendTo" ) { + this.menu.element.appendTo( this._appendTo() ); + } + if ( key === "disabled" && value && this.xhr ) { + this.xhr.abort(); + } + }, + + _appendTo: function() { + var element = this.options.appendTo; + + if ( element ) { + element = element.jquery || element.nodeType ? + $( element ) : + this.document.find( element ).eq( 0 ); + } + + if ( !element || !element[ 0 ] ) { + element = this.element.closest( ".ui-front" ); + } + + if ( !element.length ) { + element = this.document[ 0 ].body; + } + + return element; + }, + + _initSource: function() { + var array, url, + that = this; + if ( $.isArray( this.options.source ) ) { + array = this.options.source; + this.source = function( request, response ) { + response( $.ui.autocomplete.filter( array, request.term ) ); + }; + } else if ( typeof this.options.source === "string" ) { + url = this.options.source; + this.source = function( request, response ) { + if ( that.xhr ) { + that.xhr.abort(); + } + that.xhr = $.ajax({ + url: url, + data: request, + dataType: "json", + success: function( data ) { + response( data ); + }, + error: function() { + response([]); + } + }); + }; + } else { + this.source = this.options.source; + } + }, + + _searchTimeout: function( event ) { + clearTimeout( this.searching ); + this.searching = this._delay(function() { + + // Search if the value has changed, or if the user retypes the same value (see #7434) + var equalValues = this.term === this._value(), + menuVisible = this.menu.element.is( ":visible" ), + modifierKey = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey; + + if ( !equalValues || ( equalValues && !menuVisible && !modifierKey ) ) { + this.selectedItem = null; + this.search( null, event ); + } + }, this.options.delay ); + }, + + search: function( value, event ) { + value = value != null ? value : this._value(); + + // always save the actual value, not the one passed as an argument + this.term = this._value(); + + if ( value.length < this.options.minLength ) { + return this.close( event ); + } + + if ( this._trigger( "search", event ) === false ) { + return; + } + + return this._search( value ); + }, + + _search: function( value ) { + this.pending++; + this.element.addClass( "ui-autocomplete-loading" ); + this.cancelSearch = false; + + this.source( { term: value }, this._response() ); + }, + + _response: function() { + var index = ++this.requestIndex; + + return $.proxy(function( content ) { + if ( index === this.requestIndex ) { + this.__response( content ); + } + + this.pending--; + if ( !this.pending ) { + this.element.removeClass( "ui-autocomplete-loading" ); + } + }, this ); + }, + + __response: function( content ) { + if ( content ) { + content = this._normalize( content ); + } + this._trigger( "response", null, { content: content } ); + if ( !this.options.disabled && content && content.length && !this.cancelSearch ) { + this._suggest( content ); + this._trigger( "open" ); + } else { + // use ._close() instead of .close() so we don't cancel future searches + this._close(); + } + }, + + close: function( event ) { + this.cancelSearch = true; + this._close( event ); + }, + + _close: function( event ) { + if ( this.menu.element.is( ":visible" ) ) { + this.menu.element.hide(); + this.menu.blur(); + this.isNewMenu = true; + this._trigger( "close", event ); + } + }, + + _change: function( event ) { + if ( this.previous !== this._value() ) { + this._trigger( "change", event, { item: this.selectedItem } ); + } + }, + + _normalize: function( items ) { + // assume all items have the right format when the first item is complete + if ( items.length && items[ 0 ].label && items[ 0 ].value ) { + return items; + } + return $.map( items, function( item ) { + if ( typeof item === "string" ) { + return { + label: item, + value: item + }; + } + return $.extend( {}, item, { + label: item.label || item.value, + value: item.value || item.label + }); + }); + }, + + _suggest: function( items ) { + var ul = this.menu.element.empty(); + this._renderMenu( ul, items ); + this.isNewMenu = true; + this.menu.refresh(); + + // size and position menu + ul.show(); + this._resizeMenu(); + ul.position( $.extend({ + of: this.element + }, this.options.position ) ); + + if ( this.options.autoFocus ) { + this.menu.next(); + } + }, + + _resizeMenu: function() { + var ul = this.menu.element; + ul.outerWidth( Math.max( + // Firefox wraps long text (possibly a rounding bug) + // so we add 1px to avoid the wrapping (#7513) + ul.width( "" ).outerWidth() + 1, + this.element.outerWidth() + ) ); + }, + + _renderMenu: function( ul, items ) { + var that = this; + $.each( items, function( index, item ) { + that._renderItemData( ul, item ); + }); + }, + + _renderItemData: function( ul, item ) { + return this._renderItem( ul, item ).data( "ui-autocomplete-item", item ); + }, + + _renderItem: function( ul, item ) { + return $( "
                • " ).text( item.label ).appendTo( ul ); + }, + + _move: function( direction, event ) { + if ( !this.menu.element.is( ":visible" ) ) { + this.search( null, event ); + return; + } + if ( this.menu.isFirstItem() && /^previous/.test( direction ) || + this.menu.isLastItem() && /^next/.test( direction ) ) { + + if ( !this.isMultiLine ) { + this._value( this.term ); + } + + this.menu.blur(); + return; + } + this.menu[ direction ]( event ); + }, + + widget: function() { + return this.menu.element; + }, + + _value: function() { + return this.valueMethod.apply( this.element, arguments ); + }, + + _keyEvent: function( keyEvent, event ) { + if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { + this._move( keyEvent, event ); + + // prevents moving cursor to beginning/end of the text field in some browsers + event.preventDefault(); + } + } +}); + +$.extend( $.ui.autocomplete, { + escapeRegex: function( value ) { + return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ); + }, + filter: function( array, term ) { + var matcher = new RegExp( $.ui.autocomplete.escapeRegex( term ), "i" ); + return $.grep( array, function( value ) { + return matcher.test( value.label || value.value || value ); + }); + } +}); + +// live region extension, adding a `messages` option +// NOTE: This is an experimental API. We are still investigating +// a full solution for string manipulation and internationalization. +$.widget( "ui.autocomplete", $.ui.autocomplete, { + options: { + messages: { + noResults: "No search results.", + results: function( amount ) { + return amount + ( amount > 1 ? " results are" : " result is" ) + + " available, use up and down arrow keys to navigate."; + } + } + }, + + __response: function( content ) { + var message; + this._superApply( arguments ); + if ( this.options.disabled || this.cancelSearch ) { + return; + } + if ( content && content.length ) { + message = this.options.messages.results( content.length ); + } else { + message = this.options.messages.noResults; + } + this.liveRegion.children().hide(); + $( "
                  " ).text( message ).appendTo( this.liveRegion ); + } +}); + +var autocomplete = $.ui.autocomplete; + + +/*! + * jQuery UI Button 1.11.2 + * http://jqueryui.com + * + * Copyright 2014 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/button/ + */ + + +var lastActive, + baseClasses = "ui-button ui-widget ui-state-default ui-corner-all", + typeClasses = "ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only", + formResetHandler = function() { + var form = $( this ); + setTimeout(function() { + form.find( ":ui-button" ).button( "refresh" ); + }, 1 ); + }, + radioGroup = function( radio ) { + var name = radio.name, + form = radio.form, + radios = $( [] ); + if ( name ) { + name = name.replace( /'/g, "\\'" ); + if ( form ) { + radios = $( form ).find( "[name='" + name + "'][type=radio]" ); + } else { + radios = $( "[name='" + name + "'][type=radio]", radio.ownerDocument ) + .filter(function() { + return !this.form; + }); + } + } + return radios; + }; + +$.widget( "ui.button", { + version: "1.11.2", + defaultElement: "").addClass(this._triggerClass). + html(!buttonImage ? buttonText : $("").attr( + { src:buttonImage, alt:buttonText, title:buttonText }))); + input[isRTL ? "before" : "after"](inst.trigger); + inst.trigger.click(function() { + if ($.datepicker._datepickerShowing && $.datepicker._lastInput === input[0]) { + $.datepicker._hideDatepicker(); + } else if ($.datepicker._datepickerShowing && $.datepicker._lastInput !== input[0]) { + $.datepicker._hideDatepicker(); + $.datepicker._showDatepicker(input[0]); + } else { + $.datepicker._showDatepicker(input[0]); + } + return false; + }); + } + }, + + /* Apply the maximum length for the date format. */ + _autoSize: function(inst) { + if (this._get(inst, "autoSize") && !inst.inline) { + var findMax, max, maxI, i, + date = new Date(2009, 12 - 1, 20), // Ensure double digits + dateFormat = this._get(inst, "dateFormat"); + + if (dateFormat.match(/[DM]/)) { + findMax = function(names) { + max = 0; + maxI = 0; + for (i = 0; i < names.length; i++) { + if (names[i].length > max) { + max = names[i].length; + maxI = i; + } + } + return maxI; + }; + date.setMonth(findMax(this._get(inst, (dateFormat.match(/MM/) ? + "monthNames" : "monthNamesShort")))); + date.setDate(findMax(this._get(inst, (dateFormat.match(/DD/) ? + "dayNames" : "dayNamesShort"))) + 20 - date.getDay()); + } + inst.input.attr("size", this._formatDate(inst, date).length); + } + }, + + /* Attach an inline date picker to a div. */ + _inlineDatepicker: function(target, inst) { + var divSpan = $(target); + if (divSpan.hasClass(this.markerClassName)) { + return; + } + divSpan.addClass(this.markerClassName).append(inst.dpDiv); + $.data(target, "datepicker", inst); + this._setDate(inst, this._getDefaultDate(inst), true); + this._updateDatepicker(inst); + this._updateAlternate(inst); + //If disabled option is true, disable the datepicker before showing it (see ticket #5665) + if( inst.settings.disabled ) { + this._disableDatepicker( target ); + } + // Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements + // http://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height + inst.dpDiv.css( "display", "block" ); + }, + + /* Pop-up the date picker in a "dialog" box. + * @param input element - ignored + * @param date string or Date - the initial date to display + * @param onSelect function - the function to call when a date is selected + * @param settings object - update the dialog date picker instance's settings (anonymous object) + * @param pos int[2] - coordinates for the dialog's position within the screen or + * event - with x/y coordinates or + * leave empty for default (screen centre) + * @return the manager object + */ + _dialogDatepicker: function(input, date, onSelect, settings, pos) { + var id, browserWidth, browserHeight, scrollX, scrollY, + inst = this._dialogInst; // internal instance + + if (!inst) { + this.uuid += 1; + id = "dp" + this.uuid; + this._dialogInput = $(""); + this._dialogInput.keydown(this._doKeyDown); + $("body").append(this._dialogInput); + inst = this._dialogInst = this._newInst(this._dialogInput, false); + inst.settings = {}; + $.data(this._dialogInput[0], "datepicker", inst); + } + datepicker_extendRemove(inst.settings, settings || {}); + date = (date && date.constructor === Date ? this._formatDate(inst, date) : date); + this._dialogInput.val(date); + + this._pos = (pos ? (pos.length ? pos : [pos.pageX, pos.pageY]) : null); + if (!this._pos) { + browserWidth = document.documentElement.clientWidth; + browserHeight = document.documentElement.clientHeight; + scrollX = document.documentElement.scrollLeft || document.body.scrollLeft; + scrollY = document.documentElement.scrollTop || document.body.scrollTop; + this._pos = // should use actual width/height below + [(browserWidth / 2) - 100 + scrollX, (browserHeight / 2) - 150 + scrollY]; + } + + // move input on screen for focus, but hidden behind dialog + this._dialogInput.css("left", (this._pos[0] + 20) + "px").css("top", this._pos[1] + "px"); + inst.settings.onSelect = onSelect; + this._inDialog = true; + this.dpDiv.addClass(this._dialogClass); + this._showDatepicker(this._dialogInput[0]); + if ($.blockUI) { + $.blockUI(this.dpDiv); + } + $.data(this._dialogInput[0], "datepicker", inst); + return this; + }, + + /* Detach a datepicker from its control. + * @param target element - the target input field or division or span + */ + _destroyDatepicker: function(target) { + var nodeName, + $target = $(target), + inst = $.data(target, "datepicker"); + + if (!$target.hasClass(this.markerClassName)) { + return; + } + + nodeName = target.nodeName.toLowerCase(); + $.removeData(target, "datepicker"); + if (nodeName === "input") { + inst.append.remove(); + inst.trigger.remove(); + $target.removeClass(this.markerClassName). + unbind("focus", this._showDatepicker). + unbind("keydown", this._doKeyDown). + unbind("keypress", this._doKeyPress). + unbind("keyup", this._doKeyUp); + } else if (nodeName === "div" || nodeName === "span") { + $target.removeClass(this.markerClassName).empty(); + } + }, + + /* Enable the date picker to a jQuery selection. + * @param target element - the target input field or division or span + */ + _enableDatepicker: function(target) { + var nodeName, inline, + $target = $(target), + inst = $.data(target, "datepicker"); + + if (!$target.hasClass(this.markerClassName)) { + return; + } + + nodeName = target.nodeName.toLowerCase(); + if (nodeName === "input") { + target.disabled = false; + inst.trigger.filter("button"). + each(function() { this.disabled = false; }).end(). + filter("img").css({opacity: "1.0", cursor: ""}); + } else if (nodeName === "div" || nodeName === "span") { + inline = $target.children("." + this._inlineClass); + inline.children().removeClass("ui-state-disabled"); + inline.find("select.ui-datepicker-month, select.ui-datepicker-year"). + prop("disabled", false); + } + this._disabledInputs = $.map(this._disabledInputs, + function(value) { return (value === target ? null : value); }); // delete entry + }, + + /* Disable the date picker to a jQuery selection. + * @param target element - the target input field or division or span + */ + _disableDatepicker: function(target) { + var nodeName, inline, + $target = $(target), + inst = $.data(target, "datepicker"); + + if (!$target.hasClass(this.markerClassName)) { + return; + } + + nodeName = target.nodeName.toLowerCase(); + if (nodeName === "input") { + target.disabled = true; + inst.trigger.filter("button"). + each(function() { this.disabled = true; }).end(). + filter("img").css({opacity: "0.5", cursor: "default"}); + } else if (nodeName === "div" || nodeName === "span") { + inline = $target.children("." + this._inlineClass); + inline.children().addClass("ui-state-disabled"); + inline.find("select.ui-datepicker-month, select.ui-datepicker-year"). + prop("disabled", true); + } + this._disabledInputs = $.map(this._disabledInputs, + function(value) { return (value === target ? null : value); }); // delete entry + this._disabledInputs[this._disabledInputs.length] = target; + }, + + /* Is the first field in a jQuery collection disabled as a datepicker? + * @param target element - the target input field or division or span + * @return boolean - true if disabled, false if enabled + */ + _isDisabledDatepicker: function(target) { + if (!target) { + return false; + } + for (var i = 0; i < this._disabledInputs.length; i++) { + if (this._disabledInputs[i] === target) { + return true; + } + } + return false; + }, + + /* Retrieve the instance data for the target control. + * @param target element - the target input field or division or span + * @return object - the associated instance data + * @throws error if a jQuery problem getting data + */ + _getInst: function(target) { + try { + return $.data(target, "datepicker"); + } + catch (err) { + throw "Missing instance data for this datepicker"; + } + }, + + /* Update or retrieve the settings for a date picker attached to an input field or division. + * @param target element - the target input field or division or span + * @param name object - the new settings to update or + * string - the name of the setting to change or retrieve, + * when retrieving also "all" for all instance settings or + * "defaults" for all global defaults + * @param value any - the new value for the setting + * (omit if above is an object or to retrieve a value) + */ + _optionDatepicker: function(target, name, value) { + var settings, date, minDate, maxDate, + inst = this._getInst(target); + + if (arguments.length === 2 && typeof name === "string") { + return (name === "defaults" ? $.extend({}, $.datepicker._defaults) : + (inst ? (name === "all" ? $.extend({}, inst.settings) : + this._get(inst, name)) : null)); + } + + settings = name || {}; + if (typeof name === "string") { + settings = {}; + settings[name] = value; + } + + if (inst) { + if (this._curInst === inst) { + this._hideDatepicker(); + } + + date = this._getDateDatepicker(target, true); + minDate = this._getMinMaxDate(inst, "min"); + maxDate = this._getMinMaxDate(inst, "max"); + datepicker_extendRemove(inst.settings, settings); + // reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided + if (minDate !== null && settings.dateFormat !== undefined && settings.minDate === undefined) { + inst.settings.minDate = this._formatDate(inst, minDate); + } + if (maxDate !== null && settings.dateFormat !== undefined && settings.maxDate === undefined) { + inst.settings.maxDate = this._formatDate(inst, maxDate); + } + if ( "disabled" in settings ) { + if ( settings.disabled ) { + this._disableDatepicker(target); + } else { + this._enableDatepicker(target); + } + } + this._attachments($(target), inst); + this._autoSize(inst); + this._setDate(inst, date); + this._updateAlternate(inst); + this._updateDatepicker(inst); + } + }, + + // change method deprecated + _changeDatepicker: function(target, name, value) { + this._optionDatepicker(target, name, value); + }, + + /* Redraw the date picker attached to an input field or division. + * @param target element - the target input field or division or span + */ + _refreshDatepicker: function(target) { + var inst = this._getInst(target); + if (inst) { + this._updateDatepicker(inst); + } + }, + + /* Set the dates for a jQuery selection. + * @param target element - the target input field or division or span + * @param date Date - the new date + */ + _setDateDatepicker: function(target, date) { + var inst = this._getInst(target); + if (inst) { + this._setDate(inst, date); + this._updateDatepicker(inst); + this._updateAlternate(inst); + } + }, + + /* Get the date(s) for the first entry in a jQuery selection. + * @param target element - the target input field or division or span + * @param noDefault boolean - true if no default date is to be used + * @return Date - the current date + */ + _getDateDatepicker: function(target, noDefault) { + var inst = this._getInst(target); + if (inst && !inst.inline) { + this._setDateFromField(inst, noDefault); + } + return (inst ? this._getDate(inst) : null); + }, + + /* Handle keystrokes. */ + _doKeyDown: function(event) { + var onSelect, dateStr, sel, + inst = $.datepicker._getInst(event.target), + handled = true, + isRTL = inst.dpDiv.is(".ui-datepicker-rtl"); + + inst._keyEvent = true; + if ($.datepicker._datepickerShowing) { + switch (event.keyCode) { + case 9: $.datepicker._hideDatepicker(); + handled = false; + break; // hide on tab out + case 13: sel = $("td." + $.datepicker._dayOverClass + ":not(." + + $.datepicker._currentClass + ")", inst.dpDiv); + if (sel[0]) { + $.datepicker._selectDay(event.target, inst.selectedMonth, inst.selectedYear, sel[0]); + } + + onSelect = $.datepicker._get(inst, "onSelect"); + if (onSelect) { + dateStr = $.datepicker._formatDate(inst); + + // trigger custom callback + onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]); + } else { + $.datepicker._hideDatepicker(); + } + + return false; // don't submit the form + case 27: $.datepicker._hideDatepicker(); + break; // hide on escape + case 33: $.datepicker._adjustDate(event.target, (event.ctrlKey ? + -$.datepicker._get(inst, "stepBigMonths") : + -$.datepicker._get(inst, "stepMonths")), "M"); + break; // previous month/year on page up/+ ctrl + case 34: $.datepicker._adjustDate(event.target, (event.ctrlKey ? + +$.datepicker._get(inst, "stepBigMonths") : + +$.datepicker._get(inst, "stepMonths")), "M"); + break; // next month/year on page down/+ ctrl + case 35: if (event.ctrlKey || event.metaKey) { + $.datepicker._clearDate(event.target); + } + handled = event.ctrlKey || event.metaKey; + break; // clear on ctrl or command +end + case 36: if (event.ctrlKey || event.metaKey) { + $.datepicker._gotoToday(event.target); + } + handled = event.ctrlKey || event.metaKey; + break; // current on ctrl or command +home + case 37: if (event.ctrlKey || event.metaKey) { + $.datepicker._adjustDate(event.target, (isRTL ? +1 : -1), "D"); + } + handled = event.ctrlKey || event.metaKey; + // -1 day on ctrl or command +left + if (event.originalEvent.altKey) { + $.datepicker._adjustDate(event.target, (event.ctrlKey ? + -$.datepicker._get(inst, "stepBigMonths") : + -$.datepicker._get(inst, "stepMonths")), "M"); + } + // next month/year on alt +left on Mac + break; + case 38: if (event.ctrlKey || event.metaKey) { + $.datepicker._adjustDate(event.target, -7, "D"); + } + handled = event.ctrlKey || event.metaKey; + break; // -1 week on ctrl or command +up + case 39: if (event.ctrlKey || event.metaKey) { + $.datepicker._adjustDate(event.target, (isRTL ? -1 : +1), "D"); + } + handled = event.ctrlKey || event.metaKey; + // +1 day on ctrl or command +right + if (event.originalEvent.altKey) { + $.datepicker._adjustDate(event.target, (event.ctrlKey ? + +$.datepicker._get(inst, "stepBigMonths") : + +$.datepicker._get(inst, "stepMonths")), "M"); + } + // next month/year on alt +right + break; + case 40: if (event.ctrlKey || event.metaKey) { + $.datepicker._adjustDate(event.target, +7, "D"); + } + handled = event.ctrlKey || event.metaKey; + break; // +1 week on ctrl or command +down + default: handled = false; + } + } else if (event.keyCode === 36 && event.ctrlKey) { // display the date picker on ctrl+home + $.datepicker._showDatepicker(this); + } else { + handled = false; + } + + if (handled) { + event.preventDefault(); + event.stopPropagation(); + } + }, + + /* Filter entered characters - based on date format. */ + _doKeyPress: function(event) { + var chars, chr, + inst = $.datepicker._getInst(event.target); + + if ($.datepicker._get(inst, "constrainInput")) { + chars = $.datepicker._possibleChars($.datepicker._get(inst, "dateFormat")); + chr = String.fromCharCode(event.charCode == null ? event.keyCode : event.charCode); + return event.ctrlKey || event.metaKey || (chr < " " || !chars || chars.indexOf(chr) > -1); + } + }, + + /* Synchronise manual entry and field/alternate field. */ + _doKeyUp: function(event) { + var date, + inst = $.datepicker._getInst(event.target); + + if (inst.input.val() !== inst.lastVal) { + try { + date = $.datepicker.parseDate($.datepicker._get(inst, "dateFormat"), + (inst.input ? inst.input.val() : null), + $.datepicker._getFormatConfig(inst)); + + if (date) { // only if valid + $.datepicker._setDateFromField(inst); + $.datepicker._updateAlternate(inst); + $.datepicker._updateDatepicker(inst); + } + } + catch (err) { + } + } + return true; + }, + + /* Pop-up the date picker for a given input field. + * If false returned from beforeShow event handler do not show. + * @param input element - the input field attached to the date picker or + * event - if triggered by focus + */ + _showDatepicker: function(input) { + input = input.target || input; + if (input.nodeName.toLowerCase() !== "input") { // find from button/image trigger + input = $("input", input.parentNode)[0]; + } + + if ($.datepicker._isDisabledDatepicker(input) || $.datepicker._lastInput === input) { // already here + return; + } + + var inst, beforeShow, beforeShowSettings, isFixed, + offset, showAnim, duration; + + inst = $.datepicker._getInst(input); + if ($.datepicker._curInst && $.datepicker._curInst !== inst) { + $.datepicker._curInst.dpDiv.stop(true, true); + if ( inst && $.datepicker._datepickerShowing ) { + $.datepicker._hideDatepicker( $.datepicker._curInst.input[0] ); + } + } + + beforeShow = $.datepicker._get(inst, "beforeShow"); + beforeShowSettings = beforeShow ? beforeShow.apply(input, [input, inst]) : {}; + if(beforeShowSettings === false){ + return; + } + datepicker_extendRemove(inst.settings, beforeShowSettings); + + inst.lastVal = null; + $.datepicker._lastInput = input; + $.datepicker._setDateFromField(inst); + + if ($.datepicker._inDialog) { // hide cursor + input.value = ""; + } + if (!$.datepicker._pos) { // position below input + $.datepicker._pos = $.datepicker._findPos(input); + $.datepicker._pos[1] += input.offsetHeight; // add the height + } + + isFixed = false; + $(input).parents().each(function() { + isFixed |= $(this).css("position") === "fixed"; + return !isFixed; + }); + + offset = {left: $.datepicker._pos[0], top: $.datepicker._pos[1]}; + $.datepicker._pos = null; + //to avoid flashes on Firefox + inst.dpDiv.empty(); + // determine sizing offscreen + inst.dpDiv.css({position: "absolute", display: "block", top: "-1000px"}); + $.datepicker._updateDatepicker(inst); + // fix width for dynamic number of date pickers + // and adjust position before showing + offset = $.datepicker._checkOffset(inst, offset, isFixed); + inst.dpDiv.css({position: ($.datepicker._inDialog && $.blockUI ? + "static" : (isFixed ? "fixed" : "absolute")), display: "none", + left: offset.left + "px", top: offset.top + "px"}); + + if (!inst.inline) { + showAnim = $.datepicker._get(inst, "showAnim"); + duration = $.datepicker._get(inst, "duration"); + inst.dpDiv.css( "z-index", datepicker_getZindex( $( input ) ) + 1 ); + $.datepicker._datepickerShowing = true; + + if ( $.effects && $.effects.effect[ showAnim ] ) { + inst.dpDiv.show(showAnim, $.datepicker._get(inst, "showOptions"), duration); + } else { + inst.dpDiv[showAnim || "show"](showAnim ? duration : null); + } + + if ( $.datepicker._shouldFocusInput( inst ) ) { + inst.input.focus(); + } + + $.datepicker._curInst = inst; + } + }, + + /* Generate the date picker content. */ + _updateDatepicker: function(inst) { + this.maxRows = 4; //Reset the max number of rows being displayed (see #7043) + datepicker_instActive = inst; // for delegate hover events + inst.dpDiv.empty().append(this._generateHTML(inst)); + this._attachHandlers(inst); + + var origyearshtml, + numMonths = this._getNumberOfMonths(inst), + cols = numMonths[1], + width = 17, + activeCell = inst.dpDiv.find( "." + this._dayOverClass + " a" ); + + if ( activeCell.length > 0 ) { + datepicker_handleMouseover.apply( activeCell.get( 0 ) ); + } + + inst.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""); + if (cols > 1) { + inst.dpDiv.addClass("ui-datepicker-multi-" + cols).css("width", (width * cols) + "em"); + } + inst.dpDiv[(numMonths[0] !== 1 || numMonths[1] !== 1 ? "add" : "remove") + + "Class"]("ui-datepicker-multi"); + inst.dpDiv[(this._get(inst, "isRTL") ? "add" : "remove") + + "Class"]("ui-datepicker-rtl"); + + if (inst === $.datepicker._curInst && $.datepicker._datepickerShowing && $.datepicker._shouldFocusInput( inst ) ) { + inst.input.focus(); + } + + // deffered render of the years select (to avoid flashes on Firefox) + if( inst.yearshtml ){ + origyearshtml = inst.yearshtml; + setTimeout(function(){ + //assure that inst.yearshtml didn't change. + if( origyearshtml === inst.yearshtml && inst.yearshtml ){ + inst.dpDiv.find("select.ui-datepicker-year:first").replaceWith(inst.yearshtml); + } + origyearshtml = inst.yearshtml = null; + }, 0); + } + }, + + // #6694 - don't focus the input if it's already focused + // this breaks the change event in IE + // Support: IE and jQuery <1.9 + _shouldFocusInput: function( inst ) { + return inst.input && inst.input.is( ":visible" ) && !inst.input.is( ":disabled" ) && !inst.input.is( ":focus" ); + }, + + /* Check positioning to remain on screen. */ + _checkOffset: function(inst, offset, isFixed) { + var dpWidth = inst.dpDiv.outerWidth(), + dpHeight = inst.dpDiv.outerHeight(), + inputWidth = inst.input ? inst.input.outerWidth() : 0, + inputHeight = inst.input ? inst.input.outerHeight() : 0, + viewWidth = document.documentElement.clientWidth + (isFixed ? 0 : $(document).scrollLeft()), + viewHeight = document.documentElement.clientHeight + (isFixed ? 0 : $(document).scrollTop()); + + offset.left -= (this._get(inst, "isRTL") ? (dpWidth - inputWidth) : 0); + offset.left -= (isFixed && offset.left === inst.input.offset().left) ? $(document).scrollLeft() : 0; + offset.top -= (isFixed && offset.top === (inst.input.offset().top + inputHeight)) ? $(document).scrollTop() : 0; + + // now check if datepicker is showing outside window viewport - move to a better place if so. + offset.left -= Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ? + Math.abs(offset.left + dpWidth - viewWidth) : 0); + offset.top -= Math.min(offset.top, (offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ? + Math.abs(dpHeight + inputHeight) : 0); + + return offset; + }, + + /* Find an object's position on the screen. */ + _findPos: function(obj) { + var position, + inst = this._getInst(obj), + isRTL = this._get(inst, "isRTL"); + + while (obj && (obj.type === "hidden" || obj.nodeType !== 1 || $.expr.filters.hidden(obj))) { + obj = obj[isRTL ? "previousSibling" : "nextSibling"]; + } + + position = $(obj).offset(); + return [position.left, position.top]; + }, + + /* Hide the date picker from view. + * @param input element - the input field attached to the date picker + */ + _hideDatepicker: function(input) { + var showAnim, duration, postProcess, onClose, + inst = this._curInst; + + if (!inst || (input && inst !== $.data(input, "datepicker"))) { + return; + } + + if (this._datepickerShowing) { + showAnim = this._get(inst, "showAnim"); + duration = this._get(inst, "duration"); + postProcess = function() { + $.datepicker._tidyDialog(inst); + }; + + // DEPRECATED: after BC for 1.8.x $.effects[ showAnim ] is not needed + if ( $.effects && ( $.effects.effect[ showAnim ] || $.effects[ showAnim ] ) ) { + inst.dpDiv.hide(showAnim, $.datepicker._get(inst, "showOptions"), duration, postProcess); + } else { + inst.dpDiv[(showAnim === "slideDown" ? "slideUp" : + (showAnim === "fadeIn" ? "fadeOut" : "hide"))]((showAnim ? duration : null), postProcess); + } + + if (!showAnim) { + postProcess(); + } + this._datepickerShowing = false; + + onClose = this._get(inst, "onClose"); + if (onClose) { + onClose.apply((inst.input ? inst.input[0] : null), [(inst.input ? inst.input.val() : ""), inst]); + } + + this._lastInput = null; + if (this._inDialog) { + this._dialogInput.css({ position: "absolute", left: "0", top: "-100px" }); + if ($.blockUI) { + $.unblockUI(); + $("body").append(this.dpDiv); + } + } + this._inDialog = false; + } + }, + + /* Tidy up after a dialog display. */ + _tidyDialog: function(inst) { + inst.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar"); + }, + + /* Close date picker if clicked elsewhere. */ + _checkExternalClick: function(event) { + if (!$.datepicker._curInst) { + return; + } + + var $target = $(event.target), + inst = $.datepicker._getInst($target[0]); + + if ( ( ( $target[0].id !== $.datepicker._mainDivId && + $target.parents("#" + $.datepicker._mainDivId).length === 0 && + !$target.hasClass($.datepicker.markerClassName) && + !$target.closest("." + $.datepicker._triggerClass).length && + $.datepicker._datepickerShowing && !($.datepicker._inDialog && $.blockUI) ) ) || + ( $target.hasClass($.datepicker.markerClassName) && $.datepicker._curInst !== inst ) ) { + $.datepicker._hideDatepicker(); + } + }, + + /* Adjust one of the date sub-fields. */ + _adjustDate: function(id, offset, period) { + var target = $(id), + inst = this._getInst(target[0]); + + if (this._isDisabledDatepicker(target[0])) { + return; + } + this._adjustInstDate(inst, offset + + (period === "M" ? this._get(inst, "showCurrentAtPos") : 0), // undo positioning + period); + this._updateDatepicker(inst); + }, + + /* Action for current link. */ + _gotoToday: function(id) { + var date, + target = $(id), + inst = this._getInst(target[0]); + + if (this._get(inst, "gotoCurrent") && inst.currentDay) { + inst.selectedDay = inst.currentDay; + inst.drawMonth = inst.selectedMonth = inst.currentMonth; + inst.drawYear = inst.selectedYear = inst.currentYear; + } else { + date = new Date(); + inst.selectedDay = date.getDate(); + inst.drawMonth = inst.selectedMonth = date.getMonth(); + inst.drawYear = inst.selectedYear = date.getFullYear(); + } + this._notifyChange(inst); + this._adjustDate(target); + }, + + /* Action for selecting a new month/year. */ + _selectMonthYear: function(id, select, period) { + var target = $(id), + inst = this._getInst(target[0]); + + inst["selected" + (period === "M" ? "Month" : "Year")] = + inst["draw" + (period === "M" ? "Month" : "Year")] = + parseInt(select.options[select.selectedIndex].value,10); + + this._notifyChange(inst); + this._adjustDate(target); + }, + + /* Action for selecting a day. */ + _selectDay: function(id, month, year, td) { + var inst, + target = $(id); + + if ($(td).hasClass(this._unselectableClass) || this._isDisabledDatepicker(target[0])) { + return; + } + + inst = this._getInst(target[0]); + inst.selectedDay = inst.currentDay = $("a", td).html(); + inst.selectedMonth = inst.currentMonth = month; + inst.selectedYear = inst.currentYear = year; + this._selectDate(id, this._formatDate(inst, + inst.currentDay, inst.currentMonth, inst.currentYear)); + }, + + /* Erase the input field and hide the date picker. */ + _clearDate: function(id) { + var target = $(id); + this._selectDate(target, ""); + }, + + /* Update the input field with the selected date. */ + _selectDate: function(id, dateStr) { + var onSelect, + target = $(id), + inst = this._getInst(target[0]); + + dateStr = (dateStr != null ? dateStr : this._formatDate(inst)); + if (inst.input) { + inst.input.val(dateStr); + } + this._updateAlternate(inst); + + onSelect = this._get(inst, "onSelect"); + if (onSelect) { + onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]); // trigger custom callback + } else if (inst.input) { + inst.input.trigger("change"); // fire the change event + } + + if (inst.inline){ + this._updateDatepicker(inst); + } else { + this._hideDatepicker(); + this._lastInput = inst.input[0]; + if (typeof(inst.input[0]) !== "object") { + inst.input.focus(); // restore focus + } + this._lastInput = null; + } + }, + + /* Update any alternate field to synchronise with the main field. */ + _updateAlternate: function(inst) { + var altFormat, date, dateStr, + altField = this._get(inst, "altField"); + + if (altField) { // update alternate field too + altFormat = this._get(inst, "altFormat") || this._get(inst, "dateFormat"); + date = this._getDate(inst); + dateStr = this.formatDate(altFormat, date, this._getFormatConfig(inst)); + $(altField).each(function() { $(this).val(dateStr); }); + } + }, + + /* Set as beforeShowDay function to prevent selection of weekends. + * @param date Date - the date to customise + * @return [boolean, string] - is this date selectable?, what is its CSS class? + */ + noWeekends: function(date) { + var day = date.getDay(); + return [(day > 0 && day < 6), ""]; + }, + + /* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition. + * @param date Date - the date to get the week for + * @return number - the number of the week within the year that contains this date + */ + iso8601Week: function(date) { + var time, + checkDate = new Date(date.getTime()); + + // Find Thursday of this week starting on Monday + checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); + + time = checkDate.getTime(); + checkDate.setMonth(0); // Compare with Jan 1 + checkDate.setDate(1); + return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; + }, + + /* Parse a string value into a date object. + * See formatDate below for the possible formats. + * + * @param format string - the expected format of the date + * @param value string - the date in the above format + * @param settings Object - attributes include: + * shortYearCutoff number - the cutoff year for determining the century (optional) + * dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) + * dayNames string[7] - names of the days from Sunday (optional) + * monthNamesShort string[12] - abbreviated names of the months (optional) + * monthNames string[12] - names of the months (optional) + * @return Date - the extracted date value or null if value is blank + */ + parseDate: function (format, value, settings) { + if (format == null || value == null) { + throw "Invalid arguments"; + } + + value = (typeof value === "object" ? value.toString() : value + ""); + if (value === "") { + return null; + } + + var iFormat, dim, extra, + iValue = 0, + shortYearCutoffTemp = (settings ? settings.shortYearCutoff : null) || this._defaults.shortYearCutoff, + shortYearCutoff = (typeof shortYearCutoffTemp !== "string" ? shortYearCutoffTemp : + new Date().getFullYear() % 100 + parseInt(shortYearCutoffTemp, 10)), + dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort, + dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames, + monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort, + monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames, + year = -1, + month = -1, + day = -1, + doy = -1, + literal = false, + date, + // Check whether a format character is doubled + lookAhead = function(match) { + var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match); + if (matches) { + iFormat++; + } + return matches; + }, + // Extract a number from the string value + getNumber = function(match) { + var isDoubled = lookAhead(match), + size = (match === "@" ? 14 : (match === "!" ? 20 : + (match === "y" && isDoubled ? 4 : (match === "o" ? 3 : 2)))), + minSize = (match === "y" ? size : 1), + digits = new RegExp("^\\d{" + minSize + "," + size + "}"), + num = value.substring(iValue).match(digits); + if (!num) { + throw "Missing number at position " + iValue; + } + iValue += num[0].length; + return parseInt(num[0], 10); + }, + // Extract a name from the string value and convert to an index + getName = function(match, shortNames, longNames) { + var index = -1, + names = $.map(lookAhead(match) ? longNames : shortNames, function (v, k) { + return [ [k, v] ]; + }).sort(function (a, b) { + return -(a[1].length - b[1].length); + }); + + $.each(names, function (i, pair) { + var name = pair[1]; + if (value.substr(iValue, name.length).toLowerCase() === name.toLowerCase()) { + index = pair[0]; + iValue += name.length; + return false; + } + }); + if (index !== -1) { + return index + 1; + } else { + throw "Unknown name at position " + iValue; + } + }, + // Confirm that a literal character matches the string value + checkLiteral = function() { + if (value.charAt(iValue) !== format.charAt(iFormat)) { + throw "Unexpected literal at position " + iValue; + } + iValue++; + }; + + for (iFormat = 0; iFormat < format.length; iFormat++) { + if (literal) { + if (format.charAt(iFormat) === "'" && !lookAhead("'")) { + literal = false; + } else { + checkLiteral(); + } + } else { + switch (format.charAt(iFormat)) { + case "d": + day = getNumber("d"); + break; + case "D": + getName("D", dayNamesShort, dayNames); + break; + case "o": + doy = getNumber("o"); + break; + case "m": + month = getNumber("m"); + break; + case "M": + month = getName("M", monthNamesShort, monthNames); + break; + case "y": + year = getNumber("y"); + break; + case "@": + date = new Date(getNumber("@")); + year = date.getFullYear(); + month = date.getMonth() + 1; + day = date.getDate(); + break; + case "!": + date = new Date((getNumber("!") - this._ticksTo1970) / 10000); + year = date.getFullYear(); + month = date.getMonth() + 1; + day = date.getDate(); + break; + case "'": + if (lookAhead("'")){ + checkLiteral(); + } else { + literal = true; + } + break; + default: + checkLiteral(); + } + } + } + + if (iValue < value.length){ + extra = value.substr(iValue); + if (!/^\s+/.test(extra)) { + throw "Extra/unparsed characters found in date: " + extra; + } + } + + if (year === -1) { + year = new Date().getFullYear(); + } else if (year < 100) { + year += new Date().getFullYear() - new Date().getFullYear() % 100 + + (year <= shortYearCutoff ? 0 : -100); + } + + if (doy > -1) { + month = 1; + day = doy; + do { + dim = this._getDaysInMonth(year, month - 1); + if (day <= dim) { + break; + } + month++; + day -= dim; + } while (true); + } + + date = this._daylightSavingAdjust(new Date(year, month - 1, day)); + if (date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day) { + throw "Invalid date"; // E.g. 31/02/00 + } + return date; + }, + + /* Standard date formats. */ + ATOM: "yy-mm-dd", // RFC 3339 (ISO 8601) + COOKIE: "D, dd M yy", + ISO_8601: "yy-mm-dd", + RFC_822: "D, d M y", + RFC_850: "DD, dd-M-y", + RFC_1036: "D, d M y", + RFC_1123: "D, d M yy", + RFC_2822: "D, d M yy", + RSS: "D, d M y", // RFC 822 + TICKS: "!", + TIMESTAMP: "@", + W3C: "yy-mm-dd", // ISO 8601 + + _ticksTo1970: (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) + + Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000), + + /* Format a date object into a string value. + * The format can be combinations of the following: + * d - day of month (no leading zero) + * dd - day of month (two digit) + * o - day of year (no leading zeros) + * oo - day of year (three digit) + * D - day name short + * DD - day name long + * m - month of year (no leading zero) + * mm - month of year (two digit) + * M - month name short + * MM - month name long + * y - year (two digit) + * yy - year (four digit) + * @ - Unix timestamp (ms since 01/01/1970) + * ! - Windows ticks (100ns since 01/01/0001) + * "..." - literal text + * '' - single quote + * + * @param format string - the desired format of the date + * @param date Date - the date value to format + * @param settings Object - attributes include: + * dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) + * dayNames string[7] - names of the days from Sunday (optional) + * monthNamesShort string[12] - abbreviated names of the months (optional) + * monthNames string[12] - names of the months (optional) + * @return string - the date in the above format + */ + formatDate: function (format, date, settings) { + if (!date) { + return ""; + } + + var iFormat, + dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort, + dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames, + monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort, + monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames, + // Check whether a format character is doubled + lookAhead = function(match) { + var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match); + if (matches) { + iFormat++; + } + return matches; + }, + // Format a number, with leading zero if necessary + formatNumber = function(match, value, len) { + var num = "" + value; + if (lookAhead(match)) { + while (num.length < len) { + num = "0" + num; + } + } + return num; + }, + // Format a name, short or long as requested + formatName = function(match, value, shortNames, longNames) { + return (lookAhead(match) ? longNames[value] : shortNames[value]); + }, + output = "", + literal = false; + + if (date) { + for (iFormat = 0; iFormat < format.length; iFormat++) { + if (literal) { + if (format.charAt(iFormat) === "'" && !lookAhead("'")) { + literal = false; + } else { + output += format.charAt(iFormat); + } + } else { + switch (format.charAt(iFormat)) { + case "d": + output += formatNumber("d", date.getDate(), 2); + break; + case "D": + output += formatName("D", date.getDay(), dayNamesShort, dayNames); + break; + case "o": + output += formatNumber("o", + Math.round((new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000), 3); + break; + case "m": + output += formatNumber("m", date.getMonth() + 1, 2); + break; + case "M": + output += formatName("M", date.getMonth(), monthNamesShort, monthNames); + break; + case "y": + output += (lookAhead("y") ? date.getFullYear() : + (date.getYear() % 100 < 10 ? "0" : "") + date.getYear() % 100); + break; + case "@": + output += date.getTime(); + break; + case "!": + output += date.getTime() * 10000 + this._ticksTo1970; + break; + case "'": + if (lookAhead("'")) { + output += "'"; + } else { + literal = true; + } + break; + default: + output += format.charAt(iFormat); + } + } + } + } + return output; + }, + + /* Extract all possible characters from the date format. */ + _possibleChars: function (format) { + var iFormat, + chars = "", + literal = false, + // Check whether a format character is doubled + lookAhead = function(match) { + var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match); + if (matches) { + iFormat++; + } + return matches; + }; + + for (iFormat = 0; iFormat < format.length; iFormat++) { + if (literal) { + if (format.charAt(iFormat) === "'" && !lookAhead("'")) { + literal = false; + } else { + chars += format.charAt(iFormat); + } + } else { + switch (format.charAt(iFormat)) { + case "d": case "m": case "y": case "@": + chars += "0123456789"; + break; + case "D": case "M": + return null; // Accept anything + case "'": + if (lookAhead("'")) { + chars += "'"; + } else { + literal = true; + } + break; + default: + chars += format.charAt(iFormat); + } + } + } + return chars; + }, + + /* Get a setting value, defaulting if necessary. */ + _get: function(inst, name) { + return inst.settings[name] !== undefined ? + inst.settings[name] : this._defaults[name]; + }, + + /* Parse existing date and initialise date picker. */ + _setDateFromField: function(inst, noDefault) { + if (inst.input.val() === inst.lastVal) { + return; + } + + var dateFormat = this._get(inst, "dateFormat"), + dates = inst.lastVal = inst.input ? inst.input.val() : null, + defaultDate = this._getDefaultDate(inst), + date = defaultDate, + settings = this._getFormatConfig(inst); + + try { + date = this.parseDate(dateFormat, dates, settings) || defaultDate; + } catch (event) { + dates = (noDefault ? "" : dates); + } + inst.selectedDay = date.getDate(); + inst.drawMonth = inst.selectedMonth = date.getMonth(); + inst.drawYear = inst.selectedYear = date.getFullYear(); + inst.currentDay = (dates ? date.getDate() : 0); + inst.currentMonth = (dates ? date.getMonth() : 0); + inst.currentYear = (dates ? date.getFullYear() : 0); + this._adjustInstDate(inst); + }, + + /* Retrieve the default date shown on opening. */ + _getDefaultDate: function(inst) { + return this._restrictMinMax(inst, + this._determineDate(inst, this._get(inst, "defaultDate"), new Date())); + }, + + /* A date may be specified as an exact value or a relative one. */ + _determineDate: function(inst, date, defaultDate) { + var offsetNumeric = function(offset) { + var date = new Date(); + date.setDate(date.getDate() + offset); + return date; + }, + offsetString = function(offset) { + try { + return $.datepicker.parseDate($.datepicker._get(inst, "dateFormat"), + offset, $.datepicker._getFormatConfig(inst)); + } + catch (e) { + // Ignore + } + + var date = (offset.toLowerCase().match(/^c/) ? + $.datepicker._getDate(inst) : null) || new Date(), + year = date.getFullYear(), + month = date.getMonth(), + day = date.getDate(), + pattern = /([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g, + matches = pattern.exec(offset); + + while (matches) { + switch (matches[2] || "d") { + case "d" : case "D" : + day += parseInt(matches[1],10); break; + case "w" : case "W" : + day += parseInt(matches[1],10) * 7; break; + case "m" : case "M" : + month += parseInt(matches[1],10); + day = Math.min(day, $.datepicker._getDaysInMonth(year, month)); + break; + case "y": case "Y" : + year += parseInt(matches[1],10); + day = Math.min(day, $.datepicker._getDaysInMonth(year, month)); + break; + } + matches = pattern.exec(offset); + } + return new Date(year, month, day); + }, + newDate = (date == null || date === "" ? defaultDate : (typeof date === "string" ? offsetString(date) : + (typeof date === "number" ? (isNaN(date) ? defaultDate : offsetNumeric(date)) : new Date(date.getTime())))); + + newDate = (newDate && newDate.toString() === "Invalid Date" ? defaultDate : newDate); + if (newDate) { + newDate.setHours(0); + newDate.setMinutes(0); + newDate.setSeconds(0); + newDate.setMilliseconds(0); + } + return this._daylightSavingAdjust(newDate); + }, + + /* Handle switch to/from daylight saving. + * Hours may be non-zero on daylight saving cut-over: + * > 12 when midnight changeover, but then cannot generate + * midnight datetime, so jump to 1AM, otherwise reset. + * @param date (Date) the date to check + * @return (Date) the corrected date + */ + _daylightSavingAdjust: function(date) { + if (!date) { + return null; + } + date.setHours(date.getHours() > 12 ? date.getHours() + 2 : 0); + return date; + }, + + /* Set the date(s) directly. */ + _setDate: function(inst, date, noChange) { + var clear = !date, + origMonth = inst.selectedMonth, + origYear = inst.selectedYear, + newDate = this._restrictMinMax(inst, this._determineDate(inst, date, new Date())); + + inst.selectedDay = inst.currentDay = newDate.getDate(); + inst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth(); + inst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear(); + if ((origMonth !== inst.selectedMonth || origYear !== inst.selectedYear) && !noChange) { + this._notifyChange(inst); + } + this._adjustInstDate(inst); + if (inst.input) { + inst.input.val(clear ? "" : this._formatDate(inst)); + } + }, + + /* Retrieve the date(s) directly. */ + _getDate: function(inst) { + var startDate = (!inst.currentYear || (inst.input && inst.input.val() === "") ? null : + this._daylightSavingAdjust(new Date( + inst.currentYear, inst.currentMonth, inst.currentDay))); + return startDate; + }, + + /* Attach the onxxx handlers. These are declared statically so + * they work with static code transformers like Caja. + */ + _attachHandlers: function(inst) { + var stepMonths = this._get(inst, "stepMonths"), + id = "#" + inst.id.replace( /\\\\/g, "\\" ); + inst.dpDiv.find("[data-handler]").map(function () { + var handler = { + prev: function () { + $.datepicker._adjustDate(id, -stepMonths, "M"); + }, + next: function () { + $.datepicker._adjustDate(id, +stepMonths, "M"); + }, + hide: function () { + $.datepicker._hideDatepicker(); + }, + today: function () { + $.datepicker._gotoToday(id); + }, + selectDay: function () { + $.datepicker._selectDay(id, +this.getAttribute("data-month"), +this.getAttribute("data-year"), this); + return false; + }, + selectMonth: function () { + $.datepicker._selectMonthYear(id, this, "M"); + return false; + }, + selectYear: function () { + $.datepicker._selectMonthYear(id, this, "Y"); + return false; + } + }; + $(this).bind(this.getAttribute("data-event"), handler[this.getAttribute("data-handler")]); + }); + }, + + /* Generate the HTML for the current state of the date picker. */ + _generateHTML: function(inst) { + var maxDraw, prevText, prev, nextText, next, currentText, gotoDate, + controls, buttonPanel, firstDay, showWeek, dayNames, dayNamesMin, + monthNames, monthNamesShort, beforeShowDay, showOtherMonths, + selectOtherMonths, defaultDate, html, dow, row, group, col, selectedDate, + cornerClass, calender, thead, day, daysInMonth, leadDays, curRows, numRows, + printDate, dRow, tbody, daySettings, otherMonth, unselectable, + tempDate = new Date(), + today = this._daylightSavingAdjust( + new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate())), // clear time + isRTL = this._get(inst, "isRTL"), + showButtonPanel = this._get(inst, "showButtonPanel"), + hideIfNoPrevNext = this._get(inst, "hideIfNoPrevNext"), + navigationAsDateFormat = this._get(inst, "navigationAsDateFormat"), + numMonths = this._getNumberOfMonths(inst), + showCurrentAtPos = this._get(inst, "showCurrentAtPos"), + stepMonths = this._get(inst, "stepMonths"), + isMultiMonth = (numMonths[0] !== 1 || numMonths[1] !== 1), + currentDate = this._daylightSavingAdjust((!inst.currentDay ? new Date(9999, 9, 9) : + new Date(inst.currentYear, inst.currentMonth, inst.currentDay))), + minDate = this._getMinMaxDate(inst, "min"), + maxDate = this._getMinMaxDate(inst, "max"), + drawMonth = inst.drawMonth - showCurrentAtPos, + drawYear = inst.drawYear; + + if (drawMonth < 0) { + drawMonth += 12; + drawYear--; + } + if (maxDate) { + maxDraw = this._daylightSavingAdjust(new Date(maxDate.getFullYear(), + maxDate.getMonth() - (numMonths[0] * numMonths[1]) + 1, maxDate.getDate())); + maxDraw = (minDate && maxDraw < minDate ? minDate : maxDraw); + while (this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1)) > maxDraw) { + drawMonth--; + if (drawMonth < 0) { + drawMonth = 11; + drawYear--; + } + } + } + inst.drawMonth = drawMonth; + inst.drawYear = drawYear; + + prevText = this._get(inst, "prevText"); + prevText = (!navigationAsDateFormat ? prevText : this.formatDate(prevText, + this._daylightSavingAdjust(new Date(drawYear, drawMonth - stepMonths, 1)), + this._getFormatConfig(inst))); + + prev = (this._canAdjustMonth(inst, -1, drawYear, drawMonth) ? + "" + prevText + "" : + (hideIfNoPrevNext ? "" : "" + prevText + "")); + + nextText = this._get(inst, "nextText"); + nextText = (!navigationAsDateFormat ? nextText : this.formatDate(nextText, + this._daylightSavingAdjust(new Date(drawYear, drawMonth + stepMonths, 1)), + this._getFormatConfig(inst))); + + next = (this._canAdjustMonth(inst, +1, drawYear, drawMonth) ? + "" + nextText + "" : + (hideIfNoPrevNext ? "" : "" + nextText + "")); + + currentText = this._get(inst, "currentText"); + gotoDate = (this._get(inst, "gotoCurrent") && inst.currentDay ? currentDate : today); + currentText = (!navigationAsDateFormat ? currentText : + this.formatDate(currentText, gotoDate, this._getFormatConfig(inst))); + + controls = (!inst.inline ? "" : ""); + + buttonPanel = (showButtonPanel) ? "
                  " + (isRTL ? controls : "") + + (this._isInRange(inst, gotoDate) ? "" : "") + (isRTL ? "" : controls) + "
                  " : ""; + + firstDay = parseInt(this._get(inst, "firstDay"),10); + firstDay = (isNaN(firstDay) ? 0 : firstDay); + + showWeek = this._get(inst, "showWeek"); + dayNames = this._get(inst, "dayNames"); + dayNamesMin = this._get(inst, "dayNamesMin"); + monthNames = this._get(inst, "monthNames"); + monthNamesShort = this._get(inst, "monthNamesShort"); + beforeShowDay = this._get(inst, "beforeShowDay"); + showOtherMonths = this._get(inst, "showOtherMonths"); + selectOtherMonths = this._get(inst, "selectOtherMonths"); + defaultDate = this._getDefaultDate(inst); + html = ""; + dow; + for (row = 0; row < numMonths[0]; row++) { + group = ""; + this.maxRows = 4; + for (col = 0; col < numMonths[1]; col++) { + selectedDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, inst.selectedDay)); + cornerClass = " ui-corner-all"; + calender = ""; + if (isMultiMonth) { + calender += "
                  "; + } + calender += "
                  " + + (/all|left/.test(cornerClass) && row === 0 ? (isRTL ? next : prev) : "") + + (/all|right/.test(cornerClass) && row === 0 ? (isRTL ? prev : next) : "") + + this._generateMonthYearHeader(inst, drawMonth, drawYear, minDate, maxDate, + row > 0 || col > 0, monthNames, monthNamesShort) + // draw month headers + "
                  " + + ""; + thead = (showWeek ? "" : ""); + for (dow = 0; dow < 7; dow++) { // days of the week + day = (dow + firstDay) % 7; + thead += ""; + } + calender += thead + ""; + daysInMonth = this._getDaysInMonth(drawYear, drawMonth); + if (drawYear === inst.selectedYear && drawMonth === inst.selectedMonth) { + inst.selectedDay = Math.min(inst.selectedDay, daysInMonth); + } + leadDays = (this._getFirstDayOfMonth(drawYear, drawMonth) - firstDay + 7) % 7; + curRows = Math.ceil((leadDays + daysInMonth) / 7); // calculate the number of rows to generate + numRows = (isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows); //If multiple months, use the higher number of rows (see #7043) + this.maxRows = numRows; + printDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1 - leadDays)); + for (dRow = 0; dRow < numRows; dRow++) { // create date picker rows + calender += ""; + tbody = (!showWeek ? "" : ""); + for (dow = 0; dow < 7; dow++) { // create date picker days + daySettings = (beforeShowDay ? + beforeShowDay.apply((inst.input ? inst.input[0] : null), [printDate]) : [true, ""]); + otherMonth = (printDate.getMonth() !== drawMonth); + unselectable = (otherMonth && !selectOtherMonths) || !daySettings[0] || + (minDate && printDate < minDate) || (maxDate && printDate > maxDate); + tbody += ""; // display selectable date + printDate.setDate(printDate.getDate() + 1); + printDate = this._daylightSavingAdjust(printDate); + } + calender += tbody + ""; + } + drawMonth++; + if (drawMonth > 11) { + drawMonth = 0; + drawYear++; + } + calender += "
                  " + this._get(inst, "weekHeader") + "= 5 ? " class='ui-datepicker-week-end'" : "") + ">" + + "" + dayNamesMin[day] + "
                  " + + this._get(inst, "calculateWeek")(printDate) + "" + // actions + (otherMonth && !showOtherMonths ? " " : // display for other months + (unselectable ? "" + printDate.getDate() + "" : "" + printDate.getDate() + "")) + "
                  " + (isMultiMonth ? "
                  " + + ((numMonths[0] > 0 && col === numMonths[1]-1) ? "
                  " : "") : ""); + group += calender; + } + html += group; + } + html += buttonPanel; + inst._keyEvent = false; + return html; + }, + + /* Generate the month and year header. */ + _generateMonthYearHeader: function(inst, drawMonth, drawYear, minDate, maxDate, + secondary, monthNames, monthNamesShort) { + + var inMinYear, inMaxYear, month, years, thisYear, determineYear, year, endYear, + changeMonth = this._get(inst, "changeMonth"), + changeYear = this._get(inst, "changeYear"), + showMonthAfterYear = this._get(inst, "showMonthAfterYear"), + html = "
                  ", + monthHtml = ""; + + // month selection + if (secondary || !changeMonth) { + monthHtml += "" + monthNames[drawMonth] + ""; + } else { + inMinYear = (minDate && minDate.getFullYear() === drawYear); + inMaxYear = (maxDate && maxDate.getFullYear() === drawYear); + monthHtml += ""; + } + + if (!showMonthAfterYear) { + html += monthHtml + (secondary || !(changeMonth && changeYear) ? " " : ""); + } + + // year selection + if ( !inst.yearshtml ) { + inst.yearshtml = ""; + if (secondary || !changeYear) { + html += "" + drawYear + ""; + } else { + // determine range of years to display + years = this._get(inst, "yearRange").split(":"); + thisYear = new Date().getFullYear(); + determineYear = function(value) { + var year = (value.match(/c[+\-].*/) ? drawYear + parseInt(value.substring(1), 10) : + (value.match(/[+\-].*/) ? thisYear + parseInt(value, 10) : + parseInt(value, 10))); + return (isNaN(year) ? thisYear : year); + }; + year = determineYear(years[0]); + endYear = Math.max(year, determineYear(years[1] || "")); + year = (minDate ? Math.max(year, minDate.getFullYear()) : year); + endYear = (maxDate ? Math.min(endYear, maxDate.getFullYear()) : endYear); + inst.yearshtml += ""; + + html += inst.yearshtml; + inst.yearshtml = null; + } + } + + html += this._get(inst, "yearSuffix"); + if (showMonthAfterYear) { + html += (secondary || !(changeMonth && changeYear) ? " " : "") + monthHtml; + } + html += "
                  "; // Close datepicker_header + return html; + }, + + /* Adjust one of the date sub-fields. */ + _adjustInstDate: function(inst, offset, period) { + var year = inst.drawYear + (period === "Y" ? offset : 0), + month = inst.drawMonth + (period === "M" ? offset : 0), + day = Math.min(inst.selectedDay, this._getDaysInMonth(year, month)) + (period === "D" ? offset : 0), + date = this._restrictMinMax(inst, this._daylightSavingAdjust(new Date(year, month, day))); + + inst.selectedDay = date.getDate(); + inst.drawMonth = inst.selectedMonth = date.getMonth(); + inst.drawYear = inst.selectedYear = date.getFullYear(); + if (period === "M" || period === "Y") { + this._notifyChange(inst); + } + }, + + /* Ensure a date is within any min/max bounds. */ + _restrictMinMax: function(inst, date) { + var minDate = this._getMinMaxDate(inst, "min"), + maxDate = this._getMinMaxDate(inst, "max"), + newDate = (minDate && date < minDate ? minDate : date); + return (maxDate && newDate > maxDate ? maxDate : newDate); + }, + + /* Notify change of month/year. */ + _notifyChange: function(inst) { + var onChange = this._get(inst, "onChangeMonthYear"); + if (onChange) { + onChange.apply((inst.input ? inst.input[0] : null), + [inst.selectedYear, inst.selectedMonth + 1, inst]); + } + }, + + /* Determine the number of months to show. */ + _getNumberOfMonths: function(inst) { + var numMonths = this._get(inst, "numberOfMonths"); + return (numMonths == null ? [1, 1] : (typeof numMonths === "number" ? [1, numMonths] : numMonths)); + }, + + /* Determine the current maximum date - ensure no time components are set. */ + _getMinMaxDate: function(inst, minMax) { + return this._determineDate(inst, this._get(inst, minMax + "Date"), null); + }, + + /* Find the number of days in a given month. */ + _getDaysInMonth: function(year, month) { + return 32 - this._daylightSavingAdjust(new Date(year, month, 32)).getDate(); + }, + + /* Find the day of the week of the first of a month. */ + _getFirstDayOfMonth: function(year, month) { + return new Date(year, month, 1).getDay(); + }, + + /* Determines if we should allow a "next/prev" month display change. */ + _canAdjustMonth: function(inst, offset, curYear, curMonth) { + var numMonths = this._getNumberOfMonths(inst), + date = this._daylightSavingAdjust(new Date(curYear, + curMonth + (offset < 0 ? offset : numMonths[0] * numMonths[1]), 1)); + + if (offset < 0) { + date.setDate(this._getDaysInMonth(date.getFullYear(), date.getMonth())); + } + return this._isInRange(inst, date); + }, + + /* Is the given date in the accepted range? */ + _isInRange: function(inst, date) { + var yearSplit, currentYear, + minDate = this._getMinMaxDate(inst, "min"), + maxDate = this._getMinMaxDate(inst, "max"), + minYear = null, + maxYear = null, + years = this._get(inst, "yearRange"); + if (years){ + yearSplit = years.split(":"); + currentYear = new Date().getFullYear(); + minYear = parseInt(yearSplit[0], 10); + maxYear = parseInt(yearSplit[1], 10); + if ( yearSplit[0].match(/[+\-].*/) ) { + minYear += currentYear; + } + if ( yearSplit[1].match(/[+\-].*/) ) { + maxYear += currentYear; + } + } + + return ((!minDate || date.getTime() >= minDate.getTime()) && + (!maxDate || date.getTime() <= maxDate.getTime()) && + (!minYear || date.getFullYear() >= minYear) && + (!maxYear || date.getFullYear() <= maxYear)); + }, + + /* Provide the configuration settings for formatting/parsing. */ + _getFormatConfig: function(inst) { + var shortYearCutoff = this._get(inst, "shortYearCutoff"); + shortYearCutoff = (typeof shortYearCutoff !== "string" ? shortYearCutoff : + new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10)); + return {shortYearCutoff: shortYearCutoff, + dayNamesShort: this._get(inst, "dayNamesShort"), dayNames: this._get(inst, "dayNames"), + monthNamesShort: this._get(inst, "monthNamesShort"), monthNames: this._get(inst, "monthNames")}; + }, + + /* Format the given date for display. */ + _formatDate: function(inst, day, month, year) { + if (!day) { + inst.currentDay = inst.selectedDay; + inst.currentMonth = inst.selectedMonth; + inst.currentYear = inst.selectedYear; + } + var date = (day ? (typeof day === "object" ? day : + this._daylightSavingAdjust(new Date(year, month, day))) : + this._daylightSavingAdjust(new Date(inst.currentYear, inst.currentMonth, inst.currentDay))); + return this.formatDate(this._get(inst, "dateFormat"), date, this._getFormatConfig(inst)); + } +}); + +/* + * Bind hover events for datepicker elements. + * Done via delegate so the binding only occurs once in the lifetime of the parent div. + * Global datepicker_instActive, set by _updateDatepicker allows the handlers to find their way back to the active picker. + */ +function datepicker_bindHover(dpDiv) { + var selector = "button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a"; + return dpDiv.delegate(selector, "mouseout", function() { + $(this).removeClass("ui-state-hover"); + if (this.className.indexOf("ui-datepicker-prev") !== -1) { + $(this).removeClass("ui-datepicker-prev-hover"); + } + if (this.className.indexOf("ui-datepicker-next") !== -1) { + $(this).removeClass("ui-datepicker-next-hover"); + } + }) + .delegate( selector, "mouseover", datepicker_handleMouseover ); +} + +function datepicker_handleMouseover() { + if (!$.datepicker._isDisabledDatepicker( datepicker_instActive.inline? datepicker_instActive.dpDiv.parent()[0] : datepicker_instActive.input[0])) { + $(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"); + $(this).addClass("ui-state-hover"); + if (this.className.indexOf("ui-datepicker-prev") !== -1) { + $(this).addClass("ui-datepicker-prev-hover"); + } + if (this.className.indexOf("ui-datepicker-next") !== -1) { + $(this).addClass("ui-datepicker-next-hover"); + } + } +} + +/* jQuery extend now ignores nulls! */ +function datepicker_extendRemove(target, props) { + $.extend(target, props); + for (var name in props) { + if (props[name] == null) { + target[name] = props[name]; + } + } + return target; +} + +/* Invoke the datepicker functionality. + @param options string - a command, optionally followed by additional parameters or + Object - settings for attaching new datepicker functionality + @return jQuery object */ +$.fn.datepicker = function(options){ + + /* Verify an empty collection wasn't passed - Fixes #6976 */ + if ( !this.length ) { + return this; + } + + /* Initialise the date picker. */ + if (!$.datepicker.initialized) { + $(document).mousedown($.datepicker._checkExternalClick); + $.datepicker.initialized = true; + } + + /* Append datepicker main container to body if not exist. */ + if ($("#"+$.datepicker._mainDivId).length === 0) { + $("body").append($.datepicker.dpDiv); + } + + var otherArgs = Array.prototype.slice.call(arguments, 1); + if (typeof options === "string" && (options === "isDisabled" || options === "getDate" || options === "widget")) { + return $.datepicker["_" + options + "Datepicker"]. + apply($.datepicker, [this[0]].concat(otherArgs)); + } + if (options === "option" && arguments.length === 2 && typeof arguments[1] === "string") { + return $.datepicker["_" + options + "Datepicker"]. + apply($.datepicker, [this[0]].concat(otherArgs)); + } + return this.each(function() { + typeof options === "string" ? + $.datepicker["_" + options + "Datepicker"]. + apply($.datepicker, [this].concat(otherArgs)) : + $.datepicker._attachDatepicker(this, options); + }); +}; + +$.datepicker = new Datepicker(); // singleton instance +$.datepicker.initialized = false; +$.datepicker.uuid = new Date().getTime(); +$.datepicker.version = "1.11.2"; + +var datepicker = $.datepicker; + + +/*! + * jQuery UI Draggable 1.11.2 + * http://jqueryui.com + * + * Copyright 2014 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/draggable/ + */ + + +$.widget("ui.draggable", $.ui.mouse, { + version: "1.11.2", + widgetEventPrefix: "drag", + options: { + addClasses: true, + appendTo: "parent", + axis: false, + connectToSortable: false, + containment: false, + cursor: "auto", + cursorAt: false, + grid: false, + handle: false, + helper: "original", + iframeFix: false, + opacity: false, + refreshPositions: false, + revert: false, + revertDuration: 500, + scope: "default", + scroll: true, + scrollSensitivity: 20, + scrollSpeed: 20, + snap: false, + snapMode: "both", + snapTolerance: 20, + stack: false, + zIndex: false, + + // callbacks + drag: null, + start: null, + stop: null + }, + _create: function() { + + if ( this.options.helper === "original" ) { + this._setPositionRelative(); + } + if (this.options.addClasses){ + this.element.addClass("ui-draggable"); + } + if (this.options.disabled){ + this.element.addClass("ui-draggable-disabled"); + } + this._setHandleClassName(); + + this._mouseInit(); + }, + + _setOption: function( key, value ) { + this._super( key, value ); + if ( key === "handle" ) { + this._removeHandleClassName(); + this._setHandleClassName(); + } + }, + + _destroy: function() { + if ( ( this.helper || this.element ).is( ".ui-draggable-dragging" ) ) { + this.destroyOnClear = true; + return; + } + this.element.removeClass( "ui-draggable ui-draggable-dragging ui-draggable-disabled" ); + this._removeHandleClassName(); + this._mouseDestroy(); + }, + + _mouseCapture: function(event) { + var o = this.options; + + this._blurActiveElement( event ); + + // among others, prevent a drag on a resizable-handle + if (this.helper || o.disabled || $(event.target).closest(".ui-resizable-handle").length > 0) { + return false; + } + + //Quit if we're not on a valid handle + this.handle = this._getHandle(event); + if (!this.handle) { + return false; + } + + this._blockFrames( o.iframeFix === true ? "iframe" : o.iframeFix ); + + return true; + + }, + + _blockFrames: function( selector ) { + this.iframeBlocks = this.document.find( selector ).map(function() { + var iframe = $( this ); + + return $( "
                  " ) + .css( "position", "absolute" ) + .appendTo( iframe.parent() ) + .outerWidth( iframe.outerWidth() ) + .outerHeight( iframe.outerHeight() ) + .offset( iframe.offset() )[ 0 ]; + }); + }, + + _unblockFrames: function() { + if ( this.iframeBlocks ) { + this.iframeBlocks.remove(); + delete this.iframeBlocks; + } + }, + + _blurActiveElement: function( event ) { + var document = this.document[ 0 ]; + + // Only need to blur if the event occurred on the draggable itself, see #10527 + if ( !this.handleElement.is( event.target ) ) { + return; + } + + // support: IE9 + // IE9 throws an "Unspecified error" accessing document.activeElement from an