diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..659d04d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+war/WEB-INF/classes
+war/WEB-INF/build
+war/WEB-INF/deploy
+war/WEB-INF/lib/biomed.jar
+war/biomed
+war/*.log
+*.class
+biomed.war
+gwt-unitCache/
diff --git a/bootstrap/.gitignore b/bootstrap/.gitignore
new file mode 100644
index 0000000..3b10ffb
--- /dev/null
+++ b/bootstrap/.gitignore
@@ -0,0 +1,37 @@
+# Numerous always-ignore extensions
+*.diff
+*.err
+*.orig
+*.log
+*.rej
+*.swo
+*.swp
+*.zip
+*.vi
+*~
+*.sass-cache
+
+# OS or Editor folders
+.DS_Store
+._*
+Thumbs.db
+.cache
+.project
+.settings
+.tmproj
+*.esproj
+nbproject
+*.sublime-project
+*.sublime-workspace
+
+# Komodo
+*.komodoproject
+.komodotools
+
+# Folders to ignore
+.hg
+.svn
+.CVS
+.idea
+node_modules
+dist
diff --git a/bootstrap/.issue-guidelines.js b/bootstrap/.issue-guidelines.js
new file mode 100644
index 0000000..9b20dc2
--- /dev/null
+++ b/bootstrap/.issue-guidelines.js
@@ -0,0 +1,85 @@
+/* ==========================================================
+ * issue-guidelines.js
+ * http://twitter.github.com/bootstrap/javascript.html#alerts
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+var assert = require('assert')
+
+module.exports = {
+
+ 'pull-requests': {
+
+ 'should always be made against -wip branches': function (pull) {
+ assert.ok(/\-wip$/.test(pull.base.ref))
+ },
+
+ 'should always be made from feature branches': function (pull) {
+ assert.notEqual(pull.head.ref, 'master')
+ },
+
+ 'should always include a unit test if changing js files': function (pull) {
+ var hasJS = false
+ var hasTests = false
+
+ pull.files.forEach(function (file) {
+ if (/^js\/[^./]+.js/.test(file.filename)) hasJS = true
+ if (/^js\/tests\/unit\/[^.]+.js/.test(file.filename)) hasTests = true
+ })
+
+ assert.ok(!hasJS || hasJS && hasTests)
+ },
+
+ 'after': function (pull) {
+ if (pull.reporter.stats.failures) {
+ pull.reportFailures(pull.close.bind(pull))
+ }
+ }
+
+ },
+
+ 'issues': {
+
+ 'before': function (issue) {
+ var plus = {}
+ var labels = issue.labels.map(function (label) { return label.name });
+
+ if (~labels.indexOf('popular')) return
+
+ issue.comments.forEach(function (comment) {
+ if (/\+1/.test(comment.body)) plus[comment.user.login] = true
+ })
+
+ if (Object.keys(plus).length > 5) {
+ issue.tag('popular')
+ issue.comment('Tagging this issue as popular, please stop commenting on this issue with +1. thanks!')
+ }
+ },
+
+ 'should include a jsfiddle/jsbin illustrating the problem if tagged with js but not a feature': function (issue) {
+ var labels = issue.labels.map(function (label) { return label.name });
+ if (~labels.indexOf('js') && !~labels.indexOf('feature')) assert.ok(/(jsfiddle|jsbin)/.test(issue.body))
+ },
+
+ 'after': function (issue) {
+ if (issue.reporter.stats.failures) {
+ issue.reportFailures(issue.close.bind(issue))
+ }
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/bootstrap/.travis.yml b/bootstrap/.travis.yml
new file mode 100644
index 0000000..b8e1f17
--- /dev/null
+++ b/bootstrap/.travis.yml
@@ -0,0 +1,3 @@
+language: node_js
+node_js:
+ - 0.6
\ No newline at end of file
diff --git a/bootstrap/LICENSE b/bootstrap/LICENSE
new file mode 100644
index 0000000..2bb9ad2
--- /dev/null
+++ b/bootstrap/LICENSE
@@ -0,0 +1,176 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
\ No newline at end of file
diff --git a/bootstrap/Makefile b/bootstrap/Makefile
new file mode 100644
index 0000000..95aa9b2
--- /dev/null
+++ b/bootstrap/Makefile
@@ -0,0 +1,77 @@
+BOOTSTRAP = ../css/bootstrap.css
+BOOTSTRAP_LESS = ./less/bootstrap.less
+BOOTSTRAP_RESPONSIVE = ../css/bootstrap-responsive.css
+BOOTSTRAP_RESPONSIVE_LESS = ./less/responsive.less
+DATE=$(shell date +%I:%M%p)
+CHECK=\033[32m✔\033[39m
+HR=\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#
+
+
+#
+# BUILD DOCS
+#
+
+build:
+ recess --compress less/bootstrap.less > ../src/com/biomed/resources/css/bootstrap.min.css
+ recess --compress less/bootstrap-responsive.less > ../src/com/biomed/resources/css/bootstrap-responsive.min.css
+
+ mkdir -p tmp
+ cat js/bootstrap-transition.js js/bootstrap-alert.js js/bootstrap-button.js js/bootstrap-carousel.js js/bootstrap-collapse.js js/bootstrap-dropdown.js js/bootstrap-modal.js js/bootstrap-tooltip.js js/bootstrap-popover.js js/bootstrap-scrollspy.js js/bootstrap-tab.js js/bootstrap-typeahead.js js/bootstrap-affix.js > tmp/bootstrap.js
+ uglifyjs -nc tmp/bootstrap.js > tmp/bootstrap.min.js
+ echo "/*!\n* Bootstrap.js by @fat & @mdo\n* Copyright 2012 Twitter, Inc.\n* http://www.apache.org/licenses/LICENSE-2.0.txt\n*/" > tmp/copyright.js
+ cat tmp/copyright.js tmp/bootstrap.min.js > ../src/com/biomed/resources/js/bootstrap.min.js
+
+
+#
+# CLEANS THE ROOT DIRECTORY OF PRIOR BUILDS
+#
+
+clean:
+ rm -r bootstrap
+
+#
+# BUILD SIMPLE BOOTSTRAP DIRECTORY
+# recess & uglifyjs are required
+#
+
+bootstrap:
+ mkdir -p bootstrap/img
+ mkdir -p bootstrap/css
+ mkdir -p bootstrap/js
+ cp img/* bootstrap/img/
+ recess --compile ${BOOTSTRAP_LESS} > bootstrap/css/bootstrap.css
+ recess --compress ${BOOTSTRAP_LESS} > bootstrap/css/bootstrap.min.css
+ recess --compile ${BOOTSTRAP_RESPONSIVE_LESS} > bootstrap/css/bootstrap-responsive.css
+ recess --compress ${BOOTSTRAP_RESPONSIVE_LESS} > bootstrap/css/bootstrap-responsive.min.css
+ uglifyjs -nc bootstrap/js/bootstrap.js > bootstrap/js/bootstrap.min.tmp.js
+ rm bootstrap/js/copyright.js bootstrap/js/bootstrap.min.tmp.js
+
+#
+# MAKE FOR GH-PAGES 4 FAT & MDO ONLY (O_O )
+#
+
+gh-pages: bootstrap docs
+ rm -f docs/assets/bootstrap.zip
+ zip -r docs/assets/bootstrap.zip bootstrap
+ rm -r bootstrap
+ rm -f ../bootstrap-gh-pages/assets/bootstrap.zip
+ node docs/build production
+ cp -r docs/* ../bootstrap-gh-pages
+
+#
+# WATCH LESS FILES
+#
+
+watch:
+ echo "Watching less files..."; \
+ watchr -e "watch('less/.*\.less') { system 'make' }"
+
+#
+# HAUNT GITHUB ISSUES 4 FAT & MDO ONLY (O_O )
+#
+
+haunt:
+ @haunt .issue-guidelines.js https://github.com/twitter/bootstrap
+
+
+.PHONY: docs watch gh-pages
diff --git a/bootstrap/img/glyphicons-halflings-white.png b/bootstrap/img/glyphicons-halflings-white.png
new file mode 100644
index 0000000..3bf6484
Binary files /dev/null and b/bootstrap/img/glyphicons-halflings-white.png differ
diff --git a/bootstrap/img/glyphicons-halflings.png b/bootstrap/img/glyphicons-halflings.png
new file mode 100644
index 0000000..a996999
Binary files /dev/null and b/bootstrap/img/glyphicons-halflings.png differ
diff --git a/bootstrap/js/.jshintrc b/bootstrap/js/.jshintrc
new file mode 100644
index 0000000..e072269
--- /dev/null
+++ b/bootstrap/js/.jshintrc
@@ -0,0 +1,12 @@
+{
+ "validthis": true,
+ "laxcomma" : true,
+ "laxbreak" : true,
+ "browser" : true,
+ "eqnull" : true,
+ "debug" : true,
+ "devel" : true,
+ "boss" : true,
+ "expr" : true,
+ "asi" : true
+}
\ No newline at end of file
diff --git a/bootstrap/js/bootstrap-affix.js b/bootstrap/js/bootstrap-affix.js
new file mode 100644
index 0000000..c49d6e9
--- /dev/null
+++ b/bootstrap/js/bootstrap-affix.js
@@ -0,0 +1,104 @@
+/* ==========================================================
+ * bootstrap-affix.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#affix
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* AFFIX CLASS DEFINITION
+ * ====================== */
+
+ var Affix = function (element, options) {
+ this.options = $.extend({}, $.fn.affix.defaults, options)
+ this.$window = $(window).on('scroll.affix.data-api', $.proxy(this.checkPosition, this))
+ this.$element = $(element)
+ this.checkPosition()
+ }
+
+ Affix.prototype.checkPosition = function () {
+ if (!this.$element.is(':visible')) return
+
+ var scrollHeight = $(document).height()
+ , scrollTop = this.$window.scrollTop()
+ , position = this.$element.offset()
+ , offset = this.options.offset
+ , offsetBottom = offset.bottom
+ , offsetTop = offset.top
+ , reset = 'affix affix-top affix-bottom'
+ , affix
+
+ if (typeof offset != 'object') offsetBottom = offsetTop = offset
+ if (typeof offsetTop == 'function') offsetTop = offset.top()
+ if (typeof offsetBottom == 'function') offsetBottom = offset.bottom()
+
+ affix = this.unpin != null && (scrollTop + this.unpin <= position.top) ?
+ false : offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ?
+ 'bottom' : offsetTop != null && scrollTop <= offsetTop ?
+ 'top' : false
+
+ if (this.affixed === affix) return
+
+ this.affixed = affix
+ this.unpin = affix == 'bottom' ? position.top - scrollTop : null
+
+ this.$element.removeClass(reset).addClass('affix' + (affix ? '-' + affix : ''))
+ }
+
+
+ /* AFFIX PLUGIN DEFINITION
+ * ======================= */
+
+ $.fn.affix = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('affix')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('affix', (data = new Affix(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.affix.Constructor = Affix
+
+ $.fn.affix.defaults = {
+ offset: 0
+ }
+
+
+ /* AFFIX DATA-API
+ * ============== */
+
+ $(window).on('load', function () {
+ $('[data-spy="affix"]').each(function () {
+ var $spy = $(this)
+ , data = $spy.data()
+
+ data.offset = data.offset || {}
+
+ data.offsetBottom && (data.offset.bottom = data.offsetBottom)
+ data.offsetTop && (data.offset.top = data.offsetTop)
+
+ $spy.affix(data)
+ })
+ })
+
+
+}(window.jQuery);
\ No newline at end of file
diff --git a/bootstrap/js/bootstrap-alert.js b/bootstrap/js/bootstrap-alert.js
new file mode 100644
index 0000000..51273ab
--- /dev/null
+++ b/bootstrap/js/bootstrap-alert.js
@@ -0,0 +1,90 @@
+/* ==========================================================
+ * bootstrap-alert.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#alerts
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* ALERT CLASS DEFINITION
+ * ====================== */
+
+ var dismiss = '[data-dismiss="alert"]'
+ , Alert = function (el) {
+ $(el).on('click', dismiss, this.close)
+ }
+
+ Alert.prototype.close = function (e) {
+ var $this = $(this)
+ , selector = $this.attr('data-target')
+ , $parent
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+ }
+
+ $parent = $(selector)
+
+ e && e.preventDefault()
+
+ $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent())
+
+ $parent.trigger(e = $.Event('close'))
+
+ if (e.isDefaultPrevented()) return
+
+ $parent.removeClass('in')
+
+ function removeElement() {
+ $parent
+ .trigger('closed')
+ .remove()
+ }
+
+ $.support.transition && $parent.hasClass('fade') ?
+ $parent.on($.support.transition.end, removeElement) :
+ removeElement()
+ }
+
+
+ /* ALERT PLUGIN DEFINITION
+ * ======================= */
+
+ $.fn.alert = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('alert')
+ if (!data) $this.data('alert', (data = new Alert(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ $.fn.alert.Constructor = Alert
+
+
+ /* ALERT DATA-API
+ * ============== */
+
+ $(function () {
+ $('body').on('click.alert.data-api', dismiss, Alert.prototype.close)
+ })
+
+}(window.jQuery);
\ No newline at end of file
diff --git a/bootstrap/js/bootstrap-button.js b/bootstrap/js/bootstrap-button.js
new file mode 100644
index 0000000..a0ab0bf
--- /dev/null
+++ b/bootstrap/js/bootstrap-button.js
@@ -0,0 +1,96 @@
+/* ============================================================
+ * bootstrap-button.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#buttons
+ * ============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* BUTTON PUBLIC CLASS DEFINITION
+ * ============================== */
+
+ var Button = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, $.fn.button.defaults, options)
+ }
+
+ Button.prototype.setState = function (state) {
+ var d = 'disabled'
+ , $el = this.$element
+ , data = $el.data()
+ , val = $el.is('input') ? 'val' : 'html'
+
+ state = state + 'Text'
+ data.resetText || $el.data('resetText', $el[val]())
+
+ $el[val](data[state] || this.options[state])
+
+ // push to event loop to allow forms to submit
+ setTimeout(function () {
+ state == 'loadingText' ?
+ $el.addClass(d).attr(d, d) :
+ $el.removeClass(d).removeAttr(d)
+ }, 0)
+ }
+
+ Button.prototype.toggle = function () {
+ var $parent = this.$element.closest('[data-toggle="buttons-radio"]')
+
+ $parent && $parent
+ .find('.active')
+ .removeClass('active')
+
+ this.$element.toggleClass('active')
+ }
+
+
+ /* BUTTON PLUGIN DEFINITION
+ * ======================== */
+
+ $.fn.button = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('button')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('button', (data = new Button(this, options)))
+ if (option == 'toggle') data.toggle()
+ else if (option) data.setState(option)
+ })
+ }
+
+ $.fn.button.defaults = {
+ loadingText: 'loading...'
+ }
+
+ $.fn.button.Constructor = Button
+
+
+ /* BUTTON DATA-API
+ * =============== */
+
+ $(function () {
+ $('body').on('click.button.data-api', '[data-toggle^=button]', function ( e ) {
+ var $btn = $(e.target)
+ if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
+ $btn.button('toggle')
+ })
+ })
+
+}(window.jQuery);
\ No newline at end of file
diff --git a/bootstrap/js/bootstrap-carousel.js b/bootstrap/js/bootstrap-carousel.js
new file mode 100644
index 0000000..5c194b4
--- /dev/null
+++ b/bootstrap/js/bootstrap-carousel.js
@@ -0,0 +1,176 @@
+/* ==========================================================
+ * bootstrap-carousel.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#carousel
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* CAROUSEL CLASS DEFINITION
+ * ========================= */
+
+ var Carousel = function (element, options) {
+ this.$element = $(element)
+ this.options = options
+ this.options.slide && this.slide(this.options.slide)
+ this.options.pause == 'hover' && this.$element
+ .on('mouseenter', $.proxy(this.pause, this))
+ .on('mouseleave', $.proxy(this.cycle, this))
+ }
+
+ Carousel.prototype = {
+
+ cycle: function (e) {
+ if (!e) this.paused = false
+ this.options.interval
+ && !this.paused
+ && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
+ return this
+ }
+
+ , to: function (pos) {
+ var $active = this.$element.find('.item.active')
+ , children = $active.parent().children()
+ , activePos = children.index($active)
+ , that = this
+
+ if (pos > (children.length - 1) || pos < 0) return
+
+ if (this.sliding) {
+ return this.$element.one('slid', function () {
+ that.to(pos)
+ })
+ }
+
+ if (activePos == pos) {
+ return this.pause().cycle()
+ }
+
+ return this.slide(pos > activePos ? 'next' : 'prev', $(children[pos]))
+ }
+
+ , pause: function (e) {
+ if (!e) this.paused = true
+ if (this.$element.find('.next, .prev').length && $.support.transition.end) {
+ this.$element.trigger($.support.transition.end)
+ this.cycle()
+ }
+ clearInterval(this.interval)
+ this.interval = null
+ return this
+ }
+
+ , next: function () {
+ if (this.sliding) return
+ return this.slide('next')
+ }
+
+ , prev: function () {
+ if (this.sliding) return
+ return this.slide('prev')
+ }
+
+ , slide: function (type, next) {
+ var $active = this.$element.find('.item.active')
+ , $next = next || $active[type]()
+ , isCycling = this.interval
+ , direction = type == 'next' ? 'left' : 'right'
+ , fallback = type == 'next' ? 'first' : 'last'
+ , that = this
+ , e = $.Event('slide', {
+ relatedTarget: $next[0]
+ })
+
+ this.sliding = true
+
+ isCycling && this.pause()
+
+ $next = $next.length ? $next : this.$element.find('.item')[fallback]()
+
+ if ($next.hasClass('active')) return
+
+ if ($.support.transition && this.$element.hasClass('slide')) {
+ this.$element.trigger(e)
+ if (e.isDefaultPrevented()) return
+ $next.addClass(type)
+ $next[0].offsetWidth // force reflow
+ $active.addClass(direction)
+ $next.addClass(direction)
+ this.$element.one($.support.transition.end, function () {
+ $next.removeClass([type, direction].join(' ')).addClass('active')
+ $active.removeClass(['active', direction].join(' '))
+ that.sliding = false
+ setTimeout(function () { that.$element.trigger('slid') }, 0)
+ })
+ } else {
+ this.$element.trigger(e)
+ if (e.isDefaultPrevented()) return
+ $active.removeClass('active')
+ $next.addClass('active')
+ this.sliding = false
+ this.$element.trigger('slid')
+ }
+
+ isCycling && this.cycle()
+
+ return this
+ }
+
+ }
+
+
+ /* CAROUSEL PLUGIN DEFINITION
+ * ========================== */
+
+ $.fn.carousel = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('carousel')
+ , options = $.extend({}, $.fn.carousel.defaults, typeof option == 'object' && option)
+ , action = typeof option == 'string' ? option : options.slide
+ if (!data) $this.data('carousel', (data = new Carousel(this, options)))
+ if (typeof option == 'number') data.to(option)
+ else if (action) data[action]()
+ else if (options.interval) data.cycle()
+ })
+ }
+
+ $.fn.carousel.defaults = {
+ interval: 5000
+ , pause: 'hover'
+ }
+
+ $.fn.carousel.Constructor = Carousel
+
+
+ /* CAROUSEL DATA-API
+ * ================= */
+
+ $(function () {
+ $('body').on('click.carousel.data-api', '[data-slide]', function ( e ) {
+ var $this = $(this), href
+ , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+ , options = !$target.data('modal') && $.extend({}, $target.data(), $this.data())
+ $target.carousel(options)
+ e.preventDefault()
+ })
+ })
+
+}(window.jQuery);
\ No newline at end of file
diff --git a/bootstrap/js/bootstrap-collapse.js b/bootstrap/js/bootstrap-collapse.js
new file mode 100644
index 0000000..8116f22
--- /dev/null
+++ b/bootstrap/js/bootstrap-collapse.js
@@ -0,0 +1,158 @@
+/* =============================================================
+ * bootstrap-collapse.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#collapse
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* COLLAPSE PUBLIC CLASS DEFINITION
+ * ================================ */
+
+ var Collapse = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, $.fn.collapse.defaults, options)
+
+ if (this.options.parent) {
+ this.$parent = $(this.options.parent)
+ }
+
+ this.options.toggle && this.toggle()
+ }
+
+ Collapse.prototype = {
+
+ constructor: Collapse
+
+ , dimension: function () {
+ var hasWidth = this.$element.hasClass('width')
+ return hasWidth ? 'width' : 'height'
+ }
+
+ , show: function () {
+ var dimension
+ , scroll
+ , actives
+ , hasData
+
+ if (this.transitioning) return
+
+ dimension = this.dimension()
+ scroll = $.camelCase(['scroll', dimension].join('-'))
+ actives = this.$parent && this.$parent.find('> .accordion-group > .in')
+
+ if (actives && actives.length) {
+ hasData = actives.data('collapse')
+ if (hasData && hasData.transitioning) return
+ actives.collapse('hide')
+ hasData || actives.data('collapse', null)
+ }
+
+ this.$element[dimension](0)
+ this.transition('addClass', $.Event('show'), 'shown')
+ $.support.transition && this.$element[dimension](this.$element[0][scroll])
+ }
+
+ , hide: function () {
+ var dimension
+ if (this.transitioning) return
+ dimension = this.dimension()
+ this.reset(this.$element[dimension]())
+ this.transition('removeClass', $.Event('hide'), 'hidden')
+ this.$element[dimension](0)
+ }
+
+ , reset: function (size) {
+ var dimension = this.dimension()
+
+ this.$element
+ .removeClass('collapse')
+ [dimension](size || 'auto')
+ [0].offsetWidth
+
+ this.$element[size !== null ? 'addClass' : 'removeClass']('collapse')
+
+ return this
+ }
+
+ , transition: function (method, startEvent, completeEvent) {
+ var that = this
+ , complete = function () {
+ if (startEvent.type == 'show') that.reset()
+ that.transitioning = 0
+ that.$element.trigger(completeEvent)
+ }
+
+ this.$element.trigger(startEvent)
+
+ if (startEvent.isDefaultPrevented()) return
+
+ this.transitioning = 1
+
+ this.$element[method]('in')
+
+ $.support.transition && this.$element.hasClass('collapse') ?
+ this.$element.one($.support.transition.end, complete) :
+ complete()
+ }
+
+ , toggle: function () {
+ this[this.$element.hasClass('in') ? 'hide' : 'show']()
+ }
+
+ }
+
+
+ /* COLLAPSIBLE PLUGIN DEFINITION
+ * ============================== */
+
+ $.fn.collapse = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('collapse')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('collapse', (data = new Collapse(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.collapse.defaults = {
+ toggle: true
+ }
+
+ $.fn.collapse.Constructor = Collapse
+
+
+ /* COLLAPSIBLE DATA-API
+ * ==================== */
+
+ $(function () {
+ $('body').on('click.collapse.data-api', '[data-toggle=collapse]', function (e) {
+ var $this = $(this), href
+ , target = $this.attr('data-target')
+ || e.preventDefault()
+ || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
+ , option = $(target).data('collapse') ? 'toggle' : $this.data()
+ $this[$(target).hasClass('in') ? 'addClass' : 'removeClass']('collapsed')
+ $(target).collapse(option)
+ })
+ })
+
+}(window.jQuery);
\ No newline at end of file
diff --git a/bootstrap/js/bootstrap-dropdown.js b/bootstrap/js/bootstrap-dropdown.js
new file mode 100644
index 0000000..42370df
--- /dev/null
+++ b/bootstrap/js/bootstrap-dropdown.js
@@ -0,0 +1,150 @@
+/* ============================================================
+ * bootstrap-dropdown.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#dropdowns
+ * ============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* DROPDOWN CLASS DEFINITION
+ * ========================= */
+
+ var toggle = '[data-toggle=dropdown]'
+ , Dropdown = function (element) {
+ var $el = $(element).on('click.dropdown.data-api', this.toggle)
+ $('html').on('click.dropdown.data-api', function () {
+ $el.parent().removeClass('open')
+ })
+ }
+
+ Dropdown.prototype = {
+
+ constructor: Dropdown
+
+ , toggle: function (e) {
+ var $this = $(this)
+ , $parent
+ , isActive
+
+ if ($this.is('.disabled, :disabled')) return
+
+ $parent = getParent($this)
+
+ isActive = $parent.hasClass('open')
+
+ clearMenus()
+
+ if (!isActive) {
+ $parent.toggleClass('open')
+ $this.focus()
+ }
+
+ return false
+ }
+
+ , keydown: function (e) {
+ var $this
+ , $items
+ , $active
+ , $parent
+ , isActive
+ , index
+
+ if (!/(38|40|27)/.test(e.keyCode)) return
+
+ $this = $(this)
+
+ e.preventDefault()
+ e.stopPropagation()
+
+ if ($this.is('.disabled, :disabled')) return
+
+ $parent = getParent($this)
+
+ isActive = $parent.hasClass('open')
+
+ if (!isActive || (isActive && e.keyCode == 27)) return $this.click()
+
+ $items = $('[role=menu] li:not(.divider) a', $parent)
+
+ if (!$items.length) return
+
+ index = $items.index($items.filter(':focus'))
+
+ if (e.keyCode == 38 && index > 0) index-- // up
+ if (e.keyCode == 40 && index < $items.length - 1) index++ // down
+ if (!~index) index = 0
+
+ $items
+ .eq(index)
+ .focus()
+ }
+
+ }
+
+ function clearMenus() {
+ getParent($(toggle))
+ .removeClass('open')
+ }
+
+ function getParent($this) {
+ var selector = $this.attr('data-target')
+ , $parent
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+ }
+
+ $parent = $(selector)
+ $parent.length || ($parent = $this.parent())
+
+ return $parent
+ }
+
+
+ /* DROPDOWN PLUGIN DEFINITION
+ * ========================== */
+
+ $.fn.dropdown = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('dropdown')
+ if (!data) $this.data('dropdown', (data = new Dropdown(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ $.fn.dropdown.Constructor = Dropdown
+
+
+ /* APPLY TO STANDARD DROPDOWN ELEMENTS
+ * =================================== */
+
+ $(function () {
+ $('html')
+ .on('click.dropdown.data-api touchstart.dropdown.data-api', clearMenus)
+ $('body')
+ .on('click.dropdown touchstart.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
+ .on('click.dropdown.data-api touchstart.dropdown.data-api' , toggle, Dropdown.prototype.toggle)
+ .on('keydown.dropdown.data-api touchstart.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown)
+ })
+
+}(window.jQuery);
\ No newline at end of file
diff --git a/bootstrap/js/bootstrap-modal.js b/bootstrap/js/bootstrap-modal.js
new file mode 100644
index 0000000..f1622b1
--- /dev/null
+++ b/bootstrap/js/bootstrap-modal.js
@@ -0,0 +1,239 @@
+/* =========================================================
+ * bootstrap-modal.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#modals
+ * =========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================= */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* MODAL CLASS DEFINITION
+ * ====================== */
+
+ var Modal = function (element, options) {
+ this.options = options
+ this.$element = $(element)
+ .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this))
+ this.options.remote && this.$element.find('.modal-body').load(this.options.remote)
+ }
+
+ Modal.prototype = {
+
+ constructor: Modal
+
+ , toggle: function () {
+ return this[!this.isShown ? 'show' : 'hide']()
+ }
+
+ , show: function () {
+ var that = this
+ , e = $.Event('show')
+
+ this.$element.trigger(e)
+
+ if (this.isShown || e.isDefaultPrevented()) return
+
+ $('body').addClass('modal-open')
+
+ this.isShown = true
+
+ this.escape()
+
+ this.backdrop(function () {
+ var transition = $.support.transition && that.$element.hasClass('fade')
+
+ if (!that.$element.parent().length) {
+ that.$element.appendTo(document.body) //don't move modals dom position
+ }
+
+ that.$element
+ .show()
+
+ if (transition) {
+ that.$element[0].offsetWidth // force reflow
+ }
+
+ that.$element
+ .addClass('in')
+ .attr('aria-hidden', false)
+ .focus()
+
+ that.enforceFocus()
+
+ transition ?
+ that.$element.one($.support.transition.end, function () { that.$element.trigger('shown') }) :
+ that.$element.trigger('shown')
+
+ })
+ }
+
+ , hide: function (e) {
+ e && e.preventDefault()
+
+ var that = this
+
+ e = $.Event('hide')
+
+ this.$element.trigger(e)
+
+ if (!this.isShown || e.isDefaultPrevented()) return
+
+ this.isShown = false
+
+ $('body').removeClass('modal-open')
+
+ this.escape()
+
+ $(document).off('focusin.modal')
+
+ this.$element
+ .removeClass('in')
+ .attr('aria-hidden', true)
+
+ $.support.transition && this.$element.hasClass('fade') ?
+ this.hideWithTransition() :
+ this.hideModal()
+ }
+
+ , enforceFocus: function () {
+ var that = this
+ $(document).on('focusin.modal', function (e) {
+ if (that.$element[0] !== e.target && !that.$element.has(e.target).length) {
+ that.$element.focus()
+ }
+ })
+ }
+
+ , escape: function () {
+ var that = this
+ if (this.isShown && this.options.keyboard) {
+ this.$element.on('keyup.dismiss.modal', function ( e ) {
+ e.which == 27 && that.hide()
+ })
+ } else if (!this.isShown) {
+ this.$element.off('keyup.dismiss.modal')
+ }
+ }
+
+ , hideWithTransition: function () {
+ var that = this
+ , timeout = setTimeout(function () {
+ that.$element.off($.support.transition.end)
+ that.hideModal()
+ }, 500)
+
+ this.$element.one($.support.transition.end, function () {
+ clearTimeout(timeout)
+ that.hideModal()
+ })
+ }
+
+ , hideModal: function (that) {
+ this.$element
+ .hide()
+ .trigger('hidden')
+
+ this.backdrop()
+ }
+
+ , removeBackdrop: function () {
+ this.$backdrop.remove()
+ this.$backdrop = null
+ }
+
+ , backdrop: function (callback) {
+ var that = this
+ , animate = this.$element.hasClass('fade') ? 'fade' : ''
+
+ if (this.isShown && this.options.backdrop) {
+ var doAnimate = $.support.transition && animate
+
+ this.$backdrop = $('
')
+ .appendTo(document.body)
+
+ if (this.options.backdrop != 'static') {
+ this.$backdrop.click($.proxy(this.hide, this))
+ }
+
+ if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
+
+ this.$backdrop.addClass('in')
+
+ doAnimate ?
+ this.$backdrop.one($.support.transition.end, callback) :
+ callback()
+
+ } else if (!this.isShown && this.$backdrop) {
+ this.$backdrop.removeClass('in')
+
+ $.support.transition && this.$element.hasClass('fade')?
+ this.$backdrop.one($.support.transition.end, $.proxy(this.removeBackdrop, this)) :
+ this.removeBackdrop()
+
+ } else if (callback) {
+ callback()
+ }
+ }
+ }
+
+
+ /* MODAL PLUGIN DEFINITION
+ * ======================= */
+
+ $.fn.modal = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('modal')
+ , options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option)
+ if (!data) $this.data('modal', (data = new Modal(this, options)))
+ if (typeof option == 'string') data[option]()
+ else if (options.show) data.show()
+ })
+ }
+
+ $.fn.modal.defaults = {
+ backdrop: true
+ , keyboard: true
+ , show: true
+ }
+
+ $.fn.modal.Constructor = Modal
+
+
+ /* MODAL DATA-API
+ * ============== */
+
+ $(function () {
+ $('body').on('click.modal.data-api', '[data-toggle="modal"]', function ( e ) {
+ var $this = $(this)
+ , href = $this.attr('href')
+ , $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) //strip for ie7
+ , option = $target.data('modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
+
+ e.preventDefault()
+
+ $target
+ .modal(option)
+ .one('hide', function () {
+ $this.focus()
+ })
+ })
+ })
+
+}(window.jQuery);
\ No newline at end of file
diff --git a/bootstrap/js/bootstrap-popover.js b/bootstrap/js/bootstrap-popover.js
new file mode 100644
index 0000000..9413722
--- /dev/null
+++ b/bootstrap/js/bootstrap-popover.js
@@ -0,0 +1,103 @@
+/* ===========================================================
+ * bootstrap-popover.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#popovers
+ * ===========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * =========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* POPOVER PUBLIC CLASS DEFINITION
+ * =============================== */
+
+ var Popover = function (element, options) {
+ this.init('popover', element, options)
+ }
+
+
+ /* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js
+ ========================================== */
+
+ Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, {
+
+ constructor: Popover
+
+ , setContent: function () {
+ var $tip = this.tip()
+ , title = this.getTitle()
+ , content = this.getContent()
+
+ $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
+ $tip.find('.popover-content > *')[this.options.html ? 'html' : 'text'](content)
+
+ $tip.removeClass('fade top bottom left right in')
+ }
+
+ , hasContent: function () {
+ return this.getTitle() || this.getContent()
+ }
+
+ , getContent: function () {
+ var content
+ , $e = this.$element
+ , o = this.options
+
+ content = $e.attr('data-content')
+ || (typeof o.content == 'function' ? o.content.call($e[0]) : o.content)
+
+ return content
+ }
+
+ , tip: function () {
+ if (!this.$tip) {
+ this.$tip = $(this.options.template)
+ }
+ return this.$tip
+ }
+
+ , destroy: function () {
+ this.hide().$element.off('.' + this.type).removeData(this.type)
+ }
+
+ })
+
+
+ /* POPOVER PLUGIN DEFINITION
+ * ======================= */
+
+ $.fn.popover = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('popover')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('popover', (data = new Popover(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.popover.Constructor = Popover
+
+ $.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, {
+ placement: 'right'
+ , trigger: 'click'
+ , content: ''
+ , template: ''
+ })
+
+}(window.jQuery);
\ No newline at end of file
diff --git a/bootstrap/js/bootstrap-scrollspy.js b/bootstrap/js/bootstrap-scrollspy.js
new file mode 100644
index 0000000..e740ac0
--- /dev/null
+++ b/bootstrap/js/bootstrap-scrollspy.js
@@ -0,0 +1,151 @@
+/* =============================================================
+ * bootstrap-scrollspy.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#scrollspy
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* SCROLLSPY CLASS DEFINITION
+ * ========================== */
+
+ function ScrollSpy(element, options) {
+ var process = $.proxy(this.process, this)
+ , $element = $(element).is('body') ? $(window) : $(element)
+ , href
+ this.options = $.extend({}, $.fn.scrollspy.defaults, options)
+ this.$scrollElement = $element.on('scroll.scroll-spy.data-api', process)
+ this.selector = (this.options.target
+ || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+ || '') + ' .nav li > a'
+ this.$body = $('body')
+ this.refresh()
+ this.process()
+ }
+
+ ScrollSpy.prototype = {
+
+ constructor: ScrollSpy
+
+ , refresh: function () {
+ var self = this
+ , $targets
+
+ this.offsets = $([])
+ this.targets = $([])
+
+ $targets = this.$body
+ .find(this.selector)
+ .map(function () {
+ var $el = $(this)
+ , href = $el.data('target') || $el.attr('href')
+ , $href = /^#\w/.test(href) && $(href)
+ return ( $href
+ && $href.length
+ && [[ $href.position().top, href ]] ) || null
+ })
+ .sort(function (a, b) { return a[0] - b[0] })
+ .each(function () {
+ self.offsets.push(this[0])
+ self.targets.push(this[1])
+ })
+ }
+
+ , process: function () {
+ var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
+ , scrollHeight = this.$scrollElement[0].scrollHeight || this.$body[0].scrollHeight
+ , maxScroll = scrollHeight - this.$scrollElement.height()
+ , offsets = this.offsets
+ , targets = this.targets
+ , activeTarget = this.activeTarget
+ , i
+
+ if (scrollTop >= maxScroll) {
+ return activeTarget != (i = targets.last()[0])
+ && this.activate ( i )
+ }
+
+ for (i = offsets.length; i--;) {
+ activeTarget != targets[i]
+ && scrollTop >= offsets[i]
+ && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
+ && this.activate( targets[i] )
+ }
+ }
+
+ , activate: function (target) {
+ var active
+ , selector
+
+ this.activeTarget = target
+
+ $(this.selector)
+ .parent('.active')
+ .removeClass('active')
+
+ selector = this.selector
+ + '[data-target="' + target + '"],'
+ + this.selector + '[href="' + target + '"]'
+
+ active = $(selector)
+ .parent('li')
+ .addClass('active')
+
+ if (active.parent('.dropdown-menu').length) {
+ active = active.closest('li.dropdown').addClass('active')
+ }
+
+ active.trigger('activate')
+ }
+
+ }
+
+
+ /* SCROLLSPY PLUGIN DEFINITION
+ * =========================== */
+
+ $.fn.scrollspy = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('scrollspy')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('scrollspy', (data = new ScrollSpy(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.scrollspy.Constructor = ScrollSpy
+
+ $.fn.scrollspy.defaults = {
+ offset: 10
+ }
+
+
+ /* SCROLLSPY DATA-API
+ * ================== */
+
+ $(window).on('load', function () {
+ $('[data-spy="scroll"]').each(function () {
+ var $spy = $(this)
+ $spy.scrollspy($spy.data())
+ })
+ })
+
+}(window.jQuery);
\ No newline at end of file
diff --git a/bootstrap/js/bootstrap-tab.js b/bootstrap/js/bootstrap-tab.js
new file mode 100644
index 0000000..070deb8
--- /dev/null
+++ b/bootstrap/js/bootstrap-tab.js
@@ -0,0 +1,135 @@
+/* ========================================================
+ * bootstrap-tab.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#tabs
+ * ========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ======================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* TAB CLASS DEFINITION
+ * ==================== */
+
+ var Tab = function (element) {
+ this.element = $(element)
+ }
+
+ Tab.prototype = {
+
+ constructor: Tab
+
+ , show: function () {
+ var $this = this.element
+ , $ul = $this.closest('ul:not(.dropdown-menu)')
+ , selector = $this.attr('data-target')
+ , previous
+ , $target
+ , e
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+ }
+
+ if ( $this.parent('li').hasClass('active') ) return
+
+ previous = $ul.find('.active a').last()[0]
+
+ e = $.Event('show', {
+ relatedTarget: previous
+ })
+
+ $this.trigger(e)
+
+ if (e.isDefaultPrevented()) return
+
+ $target = $(selector)
+
+ this.activate($this.parent('li'), $ul)
+ this.activate($target, $target.parent(), function () {
+ $this.trigger({
+ type: 'shown'
+ , relatedTarget: previous
+ })
+ })
+ }
+
+ , activate: function ( element, container, callback) {
+ var $active = container.find('> .active')
+ , transition = callback
+ && $.support.transition
+ && $active.hasClass('fade')
+
+ function next() {
+ $active
+ .removeClass('active')
+ .find('> .dropdown-menu > .active')
+ .removeClass('active')
+
+ element.addClass('active')
+
+ if (transition) {
+ element[0].offsetWidth // reflow for transition
+ element.addClass('in')
+ } else {
+ element.removeClass('fade')
+ }
+
+ if ( element.parent('.dropdown-menu') ) {
+ element.closest('li.dropdown').addClass('active')
+ }
+
+ callback && callback()
+ }
+
+ transition ?
+ $active.one($.support.transition.end, next) :
+ next()
+
+ $active.removeClass('in')
+ }
+ }
+
+
+ /* TAB PLUGIN DEFINITION
+ * ===================== */
+
+ $.fn.tab = function ( option ) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('tab')
+ if (!data) $this.data('tab', (data = new Tab(this)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.tab.Constructor = Tab
+
+
+ /* TAB DATA-API
+ * ============ */
+
+ $(function () {
+ $('body').on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
+ e.preventDefault()
+ $(this).tab('show')
+ })
+ })
+
+}(window.jQuery);
\ No newline at end of file
diff --git a/bootstrap/js/bootstrap-tooltip.js b/bootstrap/js/bootstrap-tooltip.js
new file mode 100644
index 0000000..ed62885
--- /dev/null
+++ b/bootstrap/js/bootstrap-tooltip.js
@@ -0,0 +1,275 @@
+/* ===========================================================
+ * bootstrap-tooltip.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#tooltips
+ * Inspired by the original jQuery.tipsy by Jason Frame
+ * ===========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* TOOLTIP PUBLIC CLASS DEFINITION
+ * =============================== */
+
+ var Tooltip = function (element, options) {
+ this.init('tooltip', element, options)
+ }
+
+ Tooltip.prototype = {
+
+ constructor: Tooltip
+
+ , init: function (type, element, options) {
+ var eventIn
+ , eventOut
+
+ this.type = type
+ this.$element = $(element)
+ this.options = this.getOptions(options)
+ this.enabled = true
+
+ if (this.options.trigger == 'click') {
+ this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
+ } else if (this.options.trigger != 'manual') {
+ eventIn = this.options.trigger == 'hover' ? 'mouseenter' : 'focus'
+ eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur'
+ this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
+ this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
+ }
+
+ this.options.selector ?
+ (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
+ this.fixTitle()
+ }
+
+ , getOptions: function (options) {
+ options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data())
+
+ if (options.delay && typeof options.delay == 'number') {
+ options.delay = {
+ show: options.delay
+ , hide: options.delay
+ }
+ }
+
+ return options
+ }
+
+ , enter: function (e) {
+ var self = $(e.currentTarget)[this.type](this._options).data(this.type)
+
+ if (!self.options.delay || !self.options.delay.show) return self.show()
+
+ clearTimeout(this.timeout)
+ self.hoverState = 'in'
+ this.timeout = setTimeout(function() {
+ if (self.hoverState == 'in') self.show()
+ }, self.options.delay.show)
+ }
+
+ , leave: function (e) {
+ var self = $(e.currentTarget)[this.type](this._options).data(this.type)
+
+ if (this.timeout) clearTimeout(this.timeout)
+ if (!self.options.delay || !self.options.delay.hide) return self.hide()
+
+ self.hoverState = 'out'
+ this.timeout = setTimeout(function() {
+ if (self.hoverState == 'out') self.hide()
+ }, self.options.delay.hide)
+ }
+
+ , show: function () {
+ var $tip
+ , inside
+ , pos
+ , actualWidth
+ , actualHeight
+ , placement
+ , tp
+
+ if (this.hasContent() && this.enabled) {
+ $tip = this.tip()
+ this.setContent()
+
+ if (this.options.animation) {
+ $tip.addClass('fade')
+ }
+
+ placement = typeof this.options.placement == 'function' ?
+ this.options.placement.call(this, $tip[0], this.$element[0]) :
+ this.options.placement
+
+ inside = /in/.test(placement)
+
+ $tip
+ .remove()
+ .css({ top: 0, left: 0, display: 'block' })
+ .appendTo(inside ? this.$element : document.body)
+
+ pos = this.getPosition(inside)
+
+ actualWidth = $tip[0].offsetWidth
+ actualHeight = $tip[0].offsetHeight
+
+ switch (inside ? placement.split(' ')[1] : placement) {
+ case 'bottom':
+ tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
+ break
+ case 'top':
+ tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
+ break
+ case 'left':
+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
+ break
+ case 'right':
+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
+ break
+ }
+
+ $tip
+ .css(tp)
+ .addClass(placement)
+ .addClass('in')
+ }
+ }
+
+ , setContent: function () {
+ var $tip = this.tip()
+ , title = this.getTitle()
+
+ $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
+ $tip.removeClass('fade in top bottom left right')
+ }
+
+ , hide: function () {
+ var that = this
+ , $tip = this.tip()
+
+ $tip.removeClass('in')
+
+ function removeWithAnimation() {
+ var timeout = setTimeout(function () {
+ $tip.off($.support.transition.end).remove()
+ }, 500)
+
+ $tip.one($.support.transition.end, function () {
+ clearTimeout(timeout)
+ $tip.remove()
+ })
+ }
+
+ $.support.transition && this.$tip.hasClass('fade') ?
+ removeWithAnimation() :
+ $tip.remove()
+
+ return this
+ }
+
+ , fixTitle: function () {
+ var $e = this.$element
+ if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
+ $e.attr('data-original-title', $e.attr('title') || '').removeAttr('title')
+ }
+ }
+
+ , hasContent: function () {
+ return this.getTitle()
+ }
+
+ , getPosition: function (inside) {
+ return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), {
+ width: this.$element[0].offsetWidth
+ , height: this.$element[0].offsetHeight
+ })
+ }
+
+ , getTitle: function () {
+ var title
+ , $e = this.$element
+ , o = this.options
+
+ title = $e.attr('data-original-title')
+ || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
+
+ return title
+ }
+
+ , tip: function () {
+ return this.$tip = this.$tip || $(this.options.template)
+ }
+
+ , validate: function () {
+ if (!this.$element[0].parentNode) {
+ this.hide()
+ this.$element = null
+ this.options = null
+ }
+ }
+
+ , enable: function () {
+ this.enabled = true
+ }
+
+ , disable: function () {
+ this.enabled = false
+ }
+
+ , toggleEnabled: function () {
+ this.enabled = !this.enabled
+ }
+
+ , toggle: function () {
+ this[this.tip().hasClass('in') ? 'hide' : 'show']()
+ }
+
+ , destroy: function () {
+ this.hide().$element.off('.' + this.type).removeData(this.type)
+ }
+
+ }
+
+
+ /* TOOLTIP PLUGIN DEFINITION
+ * ========================= */
+
+ $.fn.tooltip = function ( option ) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('tooltip')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.tooltip.Constructor = Tooltip
+
+ $.fn.tooltip.defaults = {
+ animation: true
+ , placement: 'top'
+ , selector: false
+ , template: ''
+ , trigger: 'hover'
+ , title: ''
+ , delay: 0
+ , html: true
+ }
+
+}(window.jQuery);
diff --git a/bootstrap/js/bootstrap-transition.js b/bootstrap/js/bootstrap-transition.js
new file mode 100644
index 0000000..fedc90a
--- /dev/null
+++ b/bootstrap/js/bootstrap-transition.js
@@ -0,0 +1,60 @@
+/* ===================================================
+ * bootstrap-transition.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#transitions
+ * ===================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ $(function () {
+
+ "use strict"; // jshint ;_;
+
+
+ /* CSS TRANSITION SUPPORT (http://www.modernizr.com/)
+ * ======================================================= */
+
+ $.support.transition = (function () {
+
+ var transitionEnd = (function () {
+
+ var el = document.createElement('bootstrap')
+ , transEndEventNames = {
+ 'WebkitTransition' : 'webkitTransitionEnd'
+ , 'MozTransition' : 'transitionend'
+ , 'OTransition' : 'oTransitionEnd otransitionend'
+ , 'transition' : 'transitionend'
+ }
+ , name
+
+ for (name in transEndEventNames){
+ if (el.style[name] !== undefined) {
+ return transEndEventNames[name]
+ }
+ }
+
+ }())
+
+ return transitionEnd && {
+ end: transitionEnd
+ }
+
+ })()
+
+ })
+
+}(window.jQuery);
\ No newline at end of file
diff --git a/bootstrap/js/bootstrap-typeahead.js b/bootstrap/js/bootstrap-typeahead.js
new file mode 100644
index 0000000..c2ccdea
--- /dev/null
+++ b/bootstrap/js/bootstrap-typeahead.js
@@ -0,0 +1,300 @@
+/* =============================================================
+ * bootstrap-typeahead.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#typeahead
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function($){
+
+ "use strict"; // jshint ;_;
+
+
+ /* TYPEAHEAD PUBLIC CLASS DEFINITION
+ * ================================= */
+
+ var Typeahead = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, $.fn.typeahead.defaults, options)
+ this.matcher = this.options.matcher || this.matcher
+ this.sorter = this.options.sorter || this.sorter
+ this.highlighter = this.options.highlighter || this.highlighter
+ this.updater = this.options.updater || this.updater
+ this.$menu = $(this.options.menu).appendTo('body')
+ this.source = this.options.source
+ this.shown = false
+ this.listen()
+ }
+
+ Typeahead.prototype = {
+
+ constructor: Typeahead
+
+ , select: function () {
+ var val = this.$menu.find('.active').attr('data-value')
+ this.$element
+ .val(this.updater(val))
+ .change()
+ return this.hide()
+ }
+
+ , updater: function (item) {
+ return item
+ }
+
+ , show: function () {
+ var pos = $.extend({}, this.$element.offset(), {
+ height: this.$element[0].offsetHeight
+ })
+
+ this.$menu.css({
+ top: pos.top + pos.height
+ , left: pos.left
+ })
+
+ this.$menu.show()
+ this.shown = true
+ return this
+ }
+
+ , hide: function () {
+ this.$menu.hide()
+ this.shown = false
+ return this
+ }
+
+ , lookup: function (event) {
+ var items
+
+ this.query = this.$element.val()
+
+ if (!this.query || this.query.length < this.options.minLength) {
+ return this.shown ? this.hide() : this
+ }
+
+ items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source
+
+ return items ? this.process(items) : this
+ }
+
+ , process: function (items) {
+ var that = this
+
+ items = $.grep(items, function (item) {
+ return that.matcher(item)
+ })
+
+ items = this.sorter(items)
+
+ if (!items.length) {
+ return this.shown ? this.hide() : this
+ }
+
+ return this.render(items.slice(0, this.options.items)).show()
+ }
+
+ , matcher: function (item) {
+ return ~item.toLowerCase().indexOf(this.query.toLowerCase())
+ }
+
+ , sorter: function (items) {
+ var beginswith = []
+ , caseSensitive = []
+ , caseInsensitive = []
+ , item
+
+ while (item = items.shift()) {
+ if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
+ else if (~item.indexOf(this.query)) caseSensitive.push(item)
+ else caseInsensitive.push(item)
+ }
+
+ return beginswith.concat(caseSensitive, caseInsensitive)
+ }
+
+ , highlighter: function (item) {
+ var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
+ return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
+ return '' + match + ' '
+ })
+ }
+
+ , render: function (items) {
+ var that = this
+
+ items = $(items).map(function (i, item) {
+ i = $(that.options.item).attr('data-value', item)
+ i.find('a').html(that.highlighter(item))
+ return i[0]
+ })
+
+ items.first().addClass('active')
+ this.$menu.html(items)
+ return this
+ }
+
+ , next: function (event) {
+ var active = this.$menu.find('.active').removeClass('active')
+ , next = active.next()
+
+ if (!next.length) {
+ next = $(this.$menu.find('li')[0])
+ }
+
+ next.addClass('active')
+ }
+
+ , prev: function (event) {
+ var active = this.$menu.find('.active').removeClass('active')
+ , prev = active.prev()
+
+ if (!prev.length) {
+ prev = this.$menu.find('li').last()
+ }
+
+ prev.addClass('active')
+ }
+
+ , listen: function () {
+ this.$element
+ .on('blur', $.proxy(this.blur, this))
+ .on('keypress', $.proxy(this.keypress, this))
+ .on('keyup', $.proxy(this.keyup, this))
+
+ if ($.browser.chrome || $.browser.webkit || $.browser.msie) {
+ this.$element.on('keydown', $.proxy(this.keydown, this))
+ }
+
+ this.$menu
+ .on('click', $.proxy(this.click, this))
+ .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
+ }
+
+ , move: function (e) {
+ if (!this.shown) return
+
+ switch(e.keyCode) {
+ case 9: // tab
+ case 13: // enter
+ case 27: // escape
+ e.preventDefault()
+ break
+
+ case 38: // up arrow
+ e.preventDefault()
+ this.prev()
+ break
+
+ case 40: // down arrow
+ e.preventDefault()
+ this.next()
+ break
+ }
+
+ e.stopPropagation()
+ }
+
+ , keydown: function (e) {
+ this.suppressKeyPressRepeat = !~$.inArray(e.keyCode, [40,38,9,13,27])
+ this.move(e)
+ }
+
+ , keypress: function (e) {
+ if (this.suppressKeyPressRepeat) return
+ this.move(e)
+ }
+
+ , keyup: function (e) {
+ switch(e.keyCode) {
+ case 40: // down arrow
+ case 38: // up arrow
+ break
+
+ case 9: // tab
+ case 13: // enter
+ if (!this.shown) return
+ this.select()
+ break
+
+ case 27: // escape
+ if (!this.shown) return
+ this.hide()
+ break
+
+ default:
+ this.lookup()
+ }
+
+ e.stopPropagation()
+ e.preventDefault()
+ }
+
+ , blur: function (e) {
+ var that = this
+ setTimeout(function () { that.hide() }, 150)
+ }
+
+ , click: function (e) {
+ e.stopPropagation()
+ e.preventDefault()
+ this.select()
+ }
+
+ , mouseenter: function (e) {
+ this.$menu.find('.active').removeClass('active')
+ $(e.currentTarget).addClass('active')
+ }
+
+ }
+
+
+ /* TYPEAHEAD PLUGIN DEFINITION
+ * =========================== */
+
+ $.fn.typeahead = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('typeahead')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.typeahead.defaults = {
+ source: []
+ , items: 8
+ , menu: ''
+ , item: ' '
+ , minLength: 1
+ }
+
+ $.fn.typeahead.Constructor = Typeahead
+
+
+ /* TYPEAHEAD DATA-API
+ * ================== */
+
+ $(function () {
+ $('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
+ var $this = $(this)
+ if ($this.data('typeahead')) return
+ e.preventDefault()
+ $this.typeahead($this.data())
+ })
+ })
+
+}(window.jQuery);
diff --git a/bootstrap/less/accordion.less b/bootstrap/less/accordion.less
new file mode 100644
index 0000000..c13c7be
--- /dev/null
+++ b/bootstrap/less/accordion.less
@@ -0,0 +1,34 @@
+//
+// Accordion
+// --------------------------------------------------
+
+
+// Parent container
+.accordion {
+ margin-bottom: @baseLineHeight;
+}
+
+// Group == heading + body
+.accordion-group {
+ margin-bottom: 2px;
+ border: 1px solid #e5e5e5;
+ .border-radius(4px);
+}
+.accordion-heading {
+ border-bottom: 0;
+}
+.accordion-heading .accordion-toggle {
+ display: block;
+ padding: 8px 15px;
+}
+
+// General toggle styles
+.accordion-toggle {
+ cursor: pointer;
+}
+
+// Inner needs the styles because you can't animate properly with any styles on the element
+.accordion-inner {
+ padding: 9px 15px;
+ border-top: 1px solid #e5e5e5;
+}
diff --git a/bootstrap/less/alerts.less b/bootstrap/less/alerts.less
new file mode 100644
index 0000000..6df5fd7
--- /dev/null
+++ b/bootstrap/less/alerts.less
@@ -0,0 +1,65 @@
+//
+// Alerts
+// --------------------------------------------------
+
+
+// Base styles
+// -------------------------
+
+.alert {
+ padding: 8px 35px 8px 14px;
+ margin-bottom: @baseLineHeight;
+ text-shadow: 0 1px 0 rgba(255,255,255,.5);
+ background-color: @warningBackground;
+ border: 1px solid @warningBorder;
+ .border-radius(4px);
+ color: @warningText;
+}
+.alert h4 {
+ margin: 0;
+}
+
+// Adjust close link position
+.alert .close {
+ position: relative;
+ top: -2px;
+ right: -21px;
+ line-height: @baseLineHeight;
+}
+
+
+// Alternate styles
+// -------------------------
+
+.alert-success {
+ background-color: @successBackground;
+ border-color: @successBorder;
+ color: @successText;
+}
+.alert-danger,
+.alert-error {
+ background-color: @errorBackground;
+ border-color: @errorBorder;
+ color: @errorText;
+}
+.alert-info {
+ background-color: @infoBackground;
+ border-color: @infoBorder;
+ color: @infoText;
+}
+
+
+// Block alerts
+// -------------------------
+
+.alert-block {
+ padding-top: 14px;
+ padding-bottom: 14px;
+}
+.alert-block > p,
+.alert-block > ul {
+ margin-bottom: 0;
+}
+.alert-block p + p {
+ margin-top: 5px;
+}
diff --git a/bootstrap/less/bootstrap.less b/bootstrap/less/bootstrap.less
new file mode 100644
index 0000000..52f2beb
--- /dev/null
+++ b/bootstrap/less/bootstrap.less
@@ -0,0 +1,65 @@
+/*!
+ * Bootstrap v2.1.1
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+
+// CSS Reset
+@import "reset.less";
+
+// Core variables and mixins
+@import "variables.less"; // Modify this for custom colors, font-sizes, etc
+@import "mixins.less";
+
+// Grid system and page structure
+@import "scaffolding.less";
+@import "grid.less";
+@import "layouts.less";
+
+// Base CSS
+@import "type.less";
+@import "code.less";
+@import "forms.less";
+@import "tables.less";
+
+// Components: common
+@import "sprites.less";
+@import "dropdowns.less";
+@import "wells.less";
+@import "component-animations.less";
+@import "close.less";
+
+// Components: Buttons & Alerts
+@import "buttons.less";
+@import "button-groups.less";
+@import "alerts.less"; // Note: alerts share common CSS with buttons and thus have styles in buttons.less
+
+// Components: Nav
+@import "navs.less";
+@import "navbar.less";
+@import "breadcrumbs.less";
+@import "pagination.less";
+@import "pager.less";
+
+// Components: Popovers
+@import "modals.less";
+@import "tooltip.less";
+@import "popovers.less";
+
+// Components: Misc
+@import "thumbnails.less";
+@import "labels-badges.less";
+@import "progress-bars.less";
+@import "accordion.less";
+@import "carousel.less";
+@import "hero-unit.less";
+
+// Custom
+@import "custom.less";
+
+// Utility classes
+@import "utilities.less"; // Has to be last to override when necessary
diff --git a/bootstrap/less/breadcrumbs.less b/bootstrap/less/breadcrumbs.less
new file mode 100644
index 0000000..fd269b3
--- /dev/null
+++ b/bootstrap/less/breadcrumbs.less
@@ -0,0 +1,24 @@
+//
+// Breadcrumbs
+// --------------------------------------------------
+
+
+.breadcrumb {
+ padding: 8px 15px;
+ margin: 0 0 @baseLineHeight;
+ list-style: none;
+ background-color: #f5f5f5;
+ .border-radius(4px);
+ li {
+ display: inline-block;
+ .ie7-inline-block();
+ text-shadow: 0 1px 0 @white;
+ }
+ .divider {
+ padding: 0 5px;
+ color: #ccc;
+ }
+ .active {
+ color: @grayLight;
+ }
+}
diff --git a/bootstrap/less/button-groups.less b/bootstrap/less/button-groups.less
new file mode 100644
index 0000000..62e53f3
--- /dev/null
+++ b/bootstrap/less/button-groups.less
@@ -0,0 +1,245 @@
+//
+// Button groups
+// --------------------------------------------------
+
+
+// Make the div behave like a button
+.btn-group {
+ position: relative;
+ font-size: 0; // remove as part 1 of font-size inline-block hack
+ vertical-align: middle; // match .btn alignment given font-size hack above
+ white-space: nowrap; // prevent buttons from wrapping when in tight spaces (e.g., the table on the tests page)
+ .ie7-restore-left-whitespace();
+}
+
+// Space out series of button groups
+.btn-group + .btn-group {
+ margin-left: 5px;
+}
+
+// Optional: Group multiple button groups together for a toolbar
+.btn-toolbar {
+ font-size: 0; // Hack to remove whitespace that results from using inline-block
+ margin-top: @baseLineHeight / 2;
+ margin-bottom: @baseLineHeight / 2;
+ .btn-group {
+ display: inline-block;
+ .ie7-inline-block();
+ }
+ .btn + .btn,
+ .btn-group + .btn,
+ .btn + .btn-group {
+ margin-left: 5px;
+ }
+}
+
+// Float them, remove border radius, then re-add to first and last elements
+.btn-group > .btn {
+ position: relative;
+ .border-radius(0);
+}
+.btn-group > .btn + .btn {
+ margin-left: -1px;
+}
+.btn-group > .btn,
+.btn-group > .dropdown-menu {
+ font-size: @baseFontSize; // redeclare as part 2 of font-size inline-block hack
+}
+
+// Reset fonts for other sizes
+.btn-group > .btn-mini {
+ font-size: 11px;
+}
+.btn-group > .btn-small {
+ font-size: 12px;
+}
+.btn-group > .btn-large {
+ font-size: 16px;
+}
+
+// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match
+.btn-group > .btn:first-child {
+ margin-left: 0;
+ -webkit-border-top-left-radius: 4px;
+ -moz-border-radius-topleft: 4px;
+ border-top-left-radius: 4px;
+ -webkit-border-bottom-left-radius: 4px;
+ -moz-border-radius-bottomleft: 4px;
+ border-bottom-left-radius: 4px;
+}
+// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it
+.btn-group > .btn:last-child,
+.btn-group > .dropdown-toggle {
+ -webkit-border-top-right-radius: 4px;
+ -moz-border-radius-topright: 4px;
+ border-top-right-radius: 4px;
+ -webkit-border-bottom-right-radius: 4px;
+ -moz-border-radius-bottomright: 4px;
+ border-bottom-right-radius: 4px;
+}
+// Reset corners for large buttons
+.btn-group > .btn.large:first-child {
+ margin-left: 0;
+ -webkit-border-top-left-radius: 6px;
+ -moz-border-radius-topleft: 6px;
+ border-top-left-radius: 6px;
+ -webkit-border-bottom-left-radius: 6px;
+ -moz-border-radius-bottomleft: 6px;
+ border-bottom-left-radius: 6px;
+}
+.btn-group > .btn.large:last-child,
+.btn-group > .large.dropdown-toggle {
+ -webkit-border-top-right-radius: 6px;
+ -moz-border-radius-topright: 6px;
+ border-top-right-radius: 6px;
+ -webkit-border-bottom-right-radius: 6px;
+ -moz-border-radius-bottomright: 6px;
+ border-bottom-right-radius: 6px;
+}
+
+// On hover/focus/active, bring the proper btn to front
+.btn-group > .btn:hover,
+.btn-group > .btn:focus,
+.btn-group > .btn:active,
+.btn-group > .btn.active {
+ z-index: 2;
+}
+
+// On active and open, don't show outline
+.btn-group .dropdown-toggle:active,
+.btn-group.open .dropdown-toggle {
+ outline: 0;
+}
+
+
+
+// Split button dropdowns
+// ----------------------
+
+// Give the line between buttons some depth
+.btn-group > .btn + .dropdown-toggle {
+ padding-left: 8px;
+ padding-right: 8px;
+ .box-shadow(inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05));
+ *padding-top: 5px;
+ *padding-bottom: 5px;
+}
+.btn-group > .btn-mini + .dropdown-toggle {
+ padding-left: 5px;
+ padding-right: 5px;
+ *padding-top: 2px;
+ *padding-bottom: 2px;
+}
+.btn-group > .btn-small + .dropdown-toggle {
+ *padding-top: 5px;
+ *padding-bottom: 4px;
+}
+.btn-group > .btn-large + .dropdown-toggle {
+ padding-left: 12px;
+ padding-right: 12px;
+ *padding-top: 7px;
+ *padding-bottom: 7px;
+}
+
+.btn-group.open {
+
+ // The clickable button for toggling the menu
+ // Remove the gradient and set the same inset shadow as the :active state
+ .dropdown-toggle {
+ background-image: none;
+ .box-shadow(inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05));
+ }
+
+ // Keep the hover's background when dropdown is open
+ .btn.dropdown-toggle {
+ background-color: @btnBackgroundHighlight;
+ }
+ .btn-primary.dropdown-toggle {
+ background-color: @btnPrimaryBackgroundHighlight;
+ }
+ .btn-warning.dropdown-toggle {
+ background-color: @btnWarningBackgroundHighlight;
+ }
+ .btn-danger.dropdown-toggle {
+ background-color: @btnDangerBackgroundHighlight;
+ }
+ .btn-success.dropdown-toggle {
+ background-color: @btnSuccessBackgroundHighlight;
+ }
+ .btn-info.dropdown-toggle {
+ background-color: @btnInfoBackgroundHighlight;
+ }
+ .btn-inverse.dropdown-toggle {
+ background-color: @btnInverseBackgroundHighlight;
+ }
+}
+
+
+// Reposition the caret
+.btn .caret {
+ margin-top: 8px;
+ margin-left: 0;
+}
+// Carets in other button sizes
+.btn-mini .caret,
+.btn-small .caret,
+.btn-large .caret {
+ margin-top: 6px;
+}
+.btn-large .caret {
+ border-left-width: 5px;
+ border-right-width: 5px;
+ border-top-width: 5px;
+}
+// Upside down carets for .dropup
+.dropup .btn-large .caret {
+ border-bottom: 5px solid @black;
+ border-top: 0;
+}
+
+
+
+// Account for other colors
+.btn-primary,
+.btn-warning,
+.btn-danger,
+.btn-info,
+.btn-success,
+.btn-inverse {
+ .caret {
+ border-top-color: @white;
+ border-bottom-color: @white;
+ }
+}
+
+
+
+// Vertical button groups
+// ----------------------
+
+.btn-group-vertical {
+ display: inline-block; // makes buttons only take up the width they need
+ .ie7-inline-block();
+}
+.btn-group-vertical .btn {
+ display: block;
+ float: none;
+ width: 100%;
+ .border-radius(0);
+}
+.btn-group-vertical .btn + .btn {
+ margin-left: 0;
+ margin-top: -1px;
+}
+.btn-group-vertical .btn:first-child {
+ .border-radius(4px 4px 0 0);
+}
+.btn-group-vertical .btn:last-child {
+ .border-radius(0 0 4px 4px);
+}
+.btn-group-vertical .btn-large:first-child {
+ .border-radius(6px 6px 0 0);
+}
+.btn-group-vertical .btn-large:last-child {
+ .border-radius(0 0 6px 6px);
+}
diff --git a/bootstrap/less/buttons.less b/bootstrap/less/buttons.less
new file mode 100644
index 0000000..3fd1e95
--- /dev/null
+++ b/bootstrap/less/buttons.less
@@ -0,0 +1,231 @@
+//
+// Buttons
+// --------------------------------------------------
+
+
+// Base styles
+// --------------------------------------------------
+
+// Core
+.btn {
+ display: inline-block;
+ .ie7-inline-block();
+ padding: 4px 14px;
+ margin-bottom: 0; // For input.btn
+ font-size: @baseFontSize;
+ line-height: @baseLineHeight;
+ *line-height: @baseLineHeight;
+ text-align: center;
+ vertical-align: middle;
+ cursor: pointer;
+ .buttonBackground(@btnBackground, @btnBackgroundHighlight, @grayDark, 0 1px 1px rgba(255,255,255,.75));
+ border: 1px solid @btnBorder;
+ *border: 0; // Remove the border to prevent IE7's black border on input:focus
+ border-bottom-color: darken(@btnBorder, 10%);
+ .border-radius(4px);
+ .ie7-restore-left-whitespace(); // Give IE7 some love
+ .box-shadow(inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05));
+
+ // Hover state
+ &:hover {
+ color: @grayDark;
+ text-decoration: none;
+ background-color: darken(@white, 10%);
+ *background-color: darken(@white, 15%); /* Buttons in IE7 don't get borders, so darken on hover */
+ background-position: 0 -15px;
+
+ // transition is only when going to hover, otherwise the background
+ // behind the gradient (there for IE<=9 fallback) gets mismatched
+ .transition(background-position .1s linear);
+ }
+
+ // Focus state for keyboard and accessibility
+ &:focus {
+ .tab-focus();
+ }
+
+ // Active state
+ &.active,
+ &:active {
+ background-color: darken(@white, 10%);
+ background-color: darken(@white, 15%) e("\9");
+ background-image: none;
+ outline: 0;
+ .box-shadow(inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05));
+ }
+
+ // Disabled state
+ &.disabled,
+ &[disabled] {
+ cursor: default;
+ background-color: darken(@white, 10%);
+ background-image: none;
+ .opacity(65);
+ .box-shadow(none);
+ }
+
+}
+
+
+
+// Button Sizes
+// --------------------------------------------------
+
+// Large
+.btn-large {
+ padding: 9px 14px;
+ font-size: @baseFontSize + 2px;
+ line-height: normal;
+ .border-radius(5px);
+}
+.btn-large [class^="icon-"] {
+ margin-top: 2px;
+}
+
+// Small
+.btn-small {
+ padding: 3px 9px;
+ font-size: @baseFontSize - 2px;
+ line-height: @baseLineHeight - 2px;
+}
+.btn-small [class^="icon-"] {
+ margin-top: 0;
+}
+
+// Mini
+.btn-mini {
+ padding: 2px 6px;
+ font-size: @baseFontSize - 3px;
+ line-height: @baseLineHeight - 3px;
+}
+
+
+// Block button
+// -------------------------
+
+.btn-block {
+ display: block;
+ width: 100%;
+ padding-left: 0;
+ padding-right: 0;
+ .box-sizing(border-box);
+}
+
+// Vertically space out multiple block buttons
+.btn-block + .btn-block {
+ margin-top: 5px;
+}
+
+// Specificity overrides
+input[type="submit"],
+input[type="reset"],
+input[type="button"] {
+ &.btn-block {
+ width: 100%;
+ }
+}
+
+
+
+// Alternate buttons
+// --------------------------------------------------
+
+// Provide *some* extra contrast for those who can get it
+.btn-primary.active,
+.btn-warning.active,
+.btn-danger.active,
+.btn-success.active,
+.btn-info.active,
+.btn-inverse.active {
+ color: rgba(255,255,255,.75);
+}
+
+// Set the backgrounds
+// -------------------------
+.btn {
+ // reset here as of 2.0.3 due to Recess property order
+ border-color: #c5c5c5;
+ border-color: rgba(0,0,0,.15) rgba(0,0,0,.15) rgba(0,0,0,.25);
+}
+.btn-primary {
+ .buttonBackground(@btnPrimaryBackground, @btnPrimaryBackgroundHighlight);
+}
+// Warning appears are orange
+.btn-warning {
+ .buttonBackground(@btnWarningBackground, @btnWarningBackgroundHighlight);
+}
+// Danger and error appear as red
+.btn-danger {
+ .buttonBackground(@btnDangerBackground, @btnDangerBackgroundHighlight);
+}
+// Success appears as green
+.btn-success {
+ .buttonBackground(@btnSuccessBackground, @btnSuccessBackgroundHighlight);
+}
+// Info appears as a neutral blue
+.btn-info {
+ .buttonBackground(@btnInfoBackground, @btnInfoBackgroundHighlight);
+}
+// Inverse appears as dark gray
+.btn-inverse {
+ .buttonBackground(@btnInverseBackground, @btnInverseBackgroundHighlight);
+}
+
+
+// Cross-browser Jank
+// --------------------------------------------------
+
+button.btn,
+input[type="submit"].btn {
+
+ // Firefox 3.6 only I believe
+ &::-moz-focus-inner {
+ padding: 0;
+ border: 0;
+ }
+
+ // IE7 has some default padding on button controls
+ *padding-top: 3px;
+ *padding-bottom: 3px;
+
+ &.btn-large {
+ *padding-top: 7px;
+ *padding-bottom: 7px;
+ }
+ &.btn-small {
+ *padding-top: 3px;
+ *padding-bottom: 3px;
+ }
+ &.btn-mini {
+ *padding-top: 1px;
+ *padding-bottom: 1px;
+ }
+}
+
+
+// Link buttons
+// --------------------------------------------------
+
+// Make a button look and behave like a link
+.btn-link,
+.btn-link:active,
+.btn-link[disabled] {
+ background-color: transparent;
+ background-image: none;
+ .box-shadow(none);
+}
+.btn-link {
+ border-color: transparent;
+ cursor: pointer;
+ color: @linkColor;
+ .border-radius(0);
+}
+.btn-link:hover {
+ color: @linkColorHover;
+ text-decoration: underline;
+ background-color: transparent;
+}
+.btn-link[disabled]:hover {
+ color: @grayDark;
+ text-decoration: none;
+}
diff --git a/bootstrap/less/carousel.less b/bootstrap/less/carousel.less
new file mode 100644
index 0000000..33f98ac
--- /dev/null
+++ b/bootstrap/less/carousel.less
@@ -0,0 +1,131 @@
+//
+// Carousel
+// --------------------------------------------------
+
+
+.carousel {
+ position: relative;
+ margin-bottom: @baseLineHeight;
+ line-height: 1;
+}
+
+.carousel-inner {
+ overflow: hidden;
+ width: 100%;
+ position: relative;
+}
+
+.carousel {
+
+ .item {
+ display: none;
+ position: relative;
+ .transition(.6s ease-in-out left);
+ }
+
+ // Account for jankitude on images
+ .item > img {
+ display: block;
+ line-height: 1;
+ }
+
+ .active,
+ .next,
+ .prev { display: block; }
+
+ .active {
+ left: 0;
+ }
+
+ .next,
+ .prev {
+ position: absolute;
+ top: 0;
+ width: 100%;
+ }
+
+ .next {
+ left: 100%;
+ }
+ .prev {
+ left: -100%;
+ }
+ .next.left,
+ .prev.right {
+ left: 0;
+ }
+
+ .active.left {
+ left: -100%;
+ }
+ .active.right {
+ left: 100%;
+ }
+
+}
+
+// Left/right controls for nav
+// ---------------------------
+
+.carousel-control {
+ position: absolute;
+ top: 40%;
+ left: 15px;
+ width: 40px;
+ height: 40px;
+ margin-top: -20px;
+ font-size: 60px;
+ font-weight: 100;
+ line-height: 30px;
+ color: @white;
+ text-align: center;
+ background: @grayDarker;
+ border: 3px solid @white;
+ .border-radius(23px);
+ .opacity(50);
+
+ // we can't have this transition here
+ // because webkit cancels the carousel
+ // animation if you trip this while
+ // in the middle of another animation
+ // ;_;
+ // .transition(opacity .2s linear);
+
+ // Reposition the right one
+ &.right {
+ left: auto;
+ right: 15px;
+ }
+
+ // Hover state
+ &:hover {
+ color: @white;
+ text-decoration: none;
+ .opacity(90);
+ }
+}
+
+
+// Caption for text below images
+// -----------------------------
+
+.carousel-caption {
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ padding: 15px;
+ background: @grayDark;
+ background: rgba(0,0,0,.75);
+}
+.carousel-caption h4,
+.carousel-caption p {
+ color: @white;
+ line-height: @baseLineHeight;
+}
+.carousel-caption h4 {
+ margin: 0 0 5px;
+}
+.carousel-caption p {
+ margin-bottom: 0;
+}
diff --git a/bootstrap/less/close.less b/bootstrap/less/close.less
new file mode 100644
index 0000000..c71a508
--- /dev/null
+++ b/bootstrap/less/close.less
@@ -0,0 +1,31 @@
+//
+// Close icons
+// --------------------------------------------------
+
+
+.close {
+ float: right;
+ font-size: 20px;
+ font-weight: bold;
+ line-height: @baseLineHeight;
+ color: @black;
+ text-shadow: 0 1px 0 rgba(255,255,255,1);
+ .opacity(20);
+ &:hover {
+ color: @black;
+ text-decoration: none;
+ cursor: pointer;
+ .opacity(40);
+ }
+}
+
+// Additional properties for button version
+// iOS requires the button element instead of an anchor tag.
+// If you want the anchor version, it requires `href="#"`.
+button.close {
+ padding: 0;
+ cursor: pointer;
+ background: transparent;
+ border: 0;
+ -webkit-appearance: none;
+}
\ No newline at end of file
diff --git a/bootstrap/less/code.less b/bootstrap/less/code.less
new file mode 100644
index 0000000..f1851a0
--- /dev/null
+++ b/bootstrap/less/code.less
@@ -0,0 +1,58 @@
+//
+// Code (inline and blocK)
+// --------------------------------------------------
+
+
+// Inline and block code styles
+code,
+pre {
+ padding: 0 3px 2px;
+ #font > #family > .monospace;
+ font-size: @baseFontSize - 2;
+ color: @grayDark;
+ .border-radius(3px);
+}
+
+// Inline code
+code {
+ padding: 2px 4px;
+ color: #d14;
+ background-color: #f7f7f9;
+ border: 1px solid #e1e1e8;
+}
+
+// Blocks of code
+pre {
+ display: block;
+ padding: (@baseLineHeight - 1) / 2;
+ margin: 0 0 @baseLineHeight / 2;
+ font-size: @baseFontSize - 1; // 14px to 13px
+ line-height: @baseLineHeight;
+ word-break: break-all;
+ word-wrap: break-word;
+ white-space: pre;
+ white-space: pre-wrap;
+ background-color: #f5f5f5;
+ border: 1px solid #ccc; // fallback for IE7-8
+ border: 1px solid rgba(0,0,0,.15);
+ .border-radius(4px);
+
+ // Make prettyprint styles more spaced out for readability
+ &.prettyprint {
+ margin-bottom: @baseLineHeight;
+ }
+
+ // Account for some code outputs that place code tags in pre tags
+ code {
+ padding: 0;
+ color: inherit;
+ background-color: transparent;
+ border: 0;
+ }
+}
+
+// Enable scrollable blocks of code
+.pre-scrollable {
+ max-height: 340px;
+ overflow-y: scroll;
+}
\ No newline at end of file
diff --git a/bootstrap/less/component-animations.less b/bootstrap/less/component-animations.less
new file mode 100644
index 0000000..d614263
--- /dev/null
+++ b/bootstrap/less/component-animations.less
@@ -0,0 +1,22 @@
+//
+// Component animations
+// --------------------------------------------------
+
+
+.fade {
+ opacity: 0;
+ .transition(opacity .15s linear);
+ &.in {
+ opacity: 1;
+ }
+}
+
+.collapse {
+ position: relative;
+ height: 0;
+ overflow: hidden;
+ .transition(height .35s ease);
+ &.in {
+ height: auto;
+ }
+}
diff --git a/bootstrap/less/custom.less b/bootstrap/less/custom.less
new file mode 100644
index 0000000..10c330c
--- /dev/null
+++ b/bootstrap/less/custom.less
@@ -0,0 +1,736 @@
+html.thin {
+ #content {
+ margin-left: 85px;
+ }
+
+ #sidebar {
+ width: 85px;
+ }
+}
+
+html.wide {
+ #sidebar {
+ width: 311px;
+ }
+}
+
+.widget form {
+ padding: 20px 0 0 0;
+ margin: 0;
+ .form-actions {
+ margin-bottom: 0;
+ }
+
+ legend {
+ padding-left: 20px;
+ }
+}
+
+body {
+ background: url(/resources/images/backgrounds/body.jpg);
+}
+
+.loadingPanel {
+ position: absolute;
+ top: -5px;
+ z-index: 1200;
+ background-color: #F7F7F7;
+ width: 100px;
+ box-shadow: 0 2px 2px -2px #CCC;
+ border: 1px solid #CDCDCD;
+ border-radius: 3px;
+ left: 50%;
+ padding: 10px 20px 5px 20px;
+ margin-left: -50px;
+}
+
+.errorPanel {
+ position: fixed;
+ z-index: 1200;
+ background: #BA6D6D;
+ width: 100%;
+ padding: 5px 10px;
+ color: white;
+}
+
+#top {
+ position: fixed;
+ background: url(/resources/images/backgrounds/top.jpg);
+ height: 48px;
+ width: 100%;
+ border-bottom: 1px solid #1C1C1C;
+ z-index: 1000;
+ .wrapper {
+ margin: 0 30px;
+ }
+
+ .logo {
+ position: absolute;
+ background-image: url(/resources/images/logo.svg);
+ background-repeat: no-repeat;
+ background-size: 100px 100px;
+ background-position: 0 -25px;
+ height: 48px;
+ width: 300px;
+ padding-left: 5px;
+ opacity: 0.3;
+ float: left;
+ }
+
+ .logoText {
+ float: left;
+ font-size: 18px;
+ margin-top: 12px;
+ color: #CCC;
+ }
+
+ .topNav {
+ float: right;
+ position: relative;
+ }
+
+ .userNav {
+ float: left;
+ margin: 10px 0 0 0;
+ li {
+ display: inline-block;
+ margin: 0 0 0 10px;
+ background: #262626;
+ .border-radius (50%);
+ .box-shadow (0 1px 0 #4E4E4E);
+ a {
+ display: block;
+ height: 28px;
+ width: 28px;
+ }
+
+ a.search {
+ background: url(/resources/images/icons/usernav/search.png) no-repeat 9px 9px;
+ }
+
+ a.settings {
+ background: url(/resources/images/icons/usernav/settings.png) no-repeat 9px 9px;
+ }
+
+ a.logout {
+ background: url(/resources/images/icons/usernav/logout.png) no-repeat 9px 9px;
+ }
+ }
+ }
+}
+
+#sidebar {
+ position: fixed;
+ background: url(/resources/images/backgrounds/side.jpg);
+ background-position: -15px;
+ height: 100%;
+ width: 311px;
+ top: 49px;
+ border-right: 1px solid #C3C3C3;
+ .mainNav {
+ float: left;
+ list-style: none;
+ margin-left: 0;
+ padding-top: 0;
+ text-align: center;
+ border-bottom: 1px solid #555;
+ width: 85px;
+ li {
+ border-top: 1px solid #555;
+ border-bottom: 1px solid #2E2E2E;
+ a {
+ display: block;
+ padding: 14px 0 8px 0;
+ color: #BABABA;
+ font-size: 11px;
+ width: 85px;
+ #gradient > .vertical (rgba(86, 86, 86, 0.2) , rgba(54, 54, 54, 0.2));
+ &:hover {
+ color: #F4F4F4;
+ text-decoration: none;
+ .transition (all 0.3s ease-in-out);
+ img {
+ opacity: 1;
+ .transition (all 0.3s ease-in-out);
+ }
+ }
+
+ &:active , &.active {
+ color: #F4F4F4;
+ #gradient > .vertical (rgba(128, 128, 128, 0.4) , rgba(115, 115, 115, 0.3));
+ }
+
+ span {
+ display: block;
+ padding-top: 4px;
+ text-shadow: 0 1px 0 #303030;
+ }
+ }
+
+ img {
+ opacity: 0.5;
+ }
+ }
+ }
+
+ .secNav {
+ padding-left: 85px;
+ }
+
+ .gwt-DatePicker {
+ margin: 20px auto;
+ }
+}
+
+.sub-nav {
+ border-top: 1px solid #C3C3C3;
+ margin: 10px 0 0 0;
+ padding: 0;
+ list-style: none;
+ a {
+ color: #6A6A6A;
+ padding: 4px 14px 8px 10px;
+ display: block;
+ border-top: 1px solid #FDFDFD;
+ border-bottom: 1px solid #CECECE;
+ font-weight: bold;
+ font-size: 11px;
+ #gradient > .vertical (#f7f7f7 , #e6e6e6);
+ &:hover {
+ text-decoration: none;
+ #gradient > .vertical (#fbfbfb , #e8e8e8);
+ }
+
+ i {
+ position: relative;
+ margin-right: 0;
+ font-size: 12px;
+ top: 4px;
+ }
+ }
+
+ li.active {
+ border-left: 4px solid #505050;
+ a {
+ background: url(/resources/images/backgrounds/body.jpg);
+ margin-right: -1px;
+ color: #A64949;
+ padding-left: 6px;
+ }
+ }
+}
+
+.subNav {
+ list-style: none;
+ margin: 10px 0 20px 0;
+ border-top: 1px solid #c3c3c3;
+ li {
+ font-size: 11px;
+ position: relative;
+ a > img {
+ display: block;
+ float: left;
+ padding: 5px 10px 0 0;
+ }
+
+ a {
+ color: #6a6a6a;
+ padding: 8px 14px 8px 14px;
+ display: block;
+ border-top: 1px solid #fdfdfd;
+ border-bottom: 1px solid #cecece;
+ font-weight: bold;
+ #gradient > .vertical (#f7f7f7 , #e6e6e6);
+ &:hover {
+ #gradient > .vertical (#fbfbfb , #e8e8e8);
+ }
+
+ &:active {
+ background: #f0f0f0;
+ }
+ }
+ }
+}
+
+#content {
+ .wrapper {
+ margin: 0 30px;
+ }
+}
+
+.scheduler {
+ margin: 0 -30px;
+ position: fixed;
+ top: 49px;
+ bottom: 0;
+ img {
+ max-width: none ! important;
+ }
+}
+
+.frequency {
+ margin: 20px 0 0 0;
+ width: auto;
+
+ .name {
+ text-align: right;
+ background: white;
+ }
+
+ th {
+ background: white;
+ }
+
+ td {
+ text-align: center;
+ }
+
+ .icon::before {
+ font-family: FontAwesome;
+ font-weight: normal;
+ font-style: normal;
+ display: inline-block;
+ text-decoration: inherit;
+ }
+
+ .disabled {
+ width: 20px;
+ height: 20px;
+ background-color: #F9F9F9;
+ color: #CCC;
+ cursor: pointer;
+
+ .icon::before {
+ content: "\f00d";
+ }
+ }
+
+ .enabled {
+ width: 20px;
+ height: 20px;
+ background-color: #DFF0D8;
+ color: #468847;
+ cursor: pointer;
+
+ .icon::before {
+ content: "\f00c";
+ }
+ }
+
+ .controls {
+ text-align: right;
+ background: white;
+ }
+}
+
+.gwt-cal {
+ position: relative;
+ white-space: nowrap;
+ overflow: hidden;
+ .major-time-interval {
+ border-top: 1px solid #D5D5D5;
+ }
+
+ .minor-time-interval {
+ border-top: 1px dotted #D5D5D5;
+ }
+
+ .working-hours {
+ background: #FFF;
+ }
+
+ .hour-label {
+ width: 50px;
+ border-top: 1px solid #DDD;
+ background: #FFF;
+ }
+
+ .hour-layout {
+ text-align: right;
+ padding: 0 4px;
+ }
+
+ .hour-text {
+ display: inline;
+ text-transform: lowercase;
+ }
+
+ .hour-noon-text {
+ display: inline;
+ text-transform: lowercase;
+ }
+
+ .ampm-text {
+ display: inline;
+ text-transform: lowercase;
+ }
+
+ .scroll-area {
+ height: 500px;
+ overflow: scroll;
+ overflow-x: hidden;
+ }
+
+ .gwt-calendar-header {
+ width: 100%;
+ border-top: 1px solid #D5D5D5;
+ border-bottom: 1px solid #D5D5D5;
+ table-layout: fixed;
+ border-collapse: collapse;
+ #gradient > .vertical (#F8F8F8 , #E8E8E8);
+ .year-cell {
+ width: 50px;
+ text-align: center;
+ }
+
+ .splitter {
+ }
+
+ .day-cell-container {
+ border-right: 1px solid #D5D5D5;
+ }
+
+ .day-cell , .day-cell-weekend {
+ position: absolute;
+ top: 0;
+ padding-top: 2px;
+ text-align: center;
+ border-left: 1px solid #D5D5D5;
+ }
+
+ .day-cell-today {
+ padding-top: 2px;
+ font-weight: bold;
+ text-align: center;
+ border-left: 1px solid #D5D5D5;
+ }
+ }
+
+ .multiDayBody {
+ width: 100%;
+ background: white;
+ table-layout: fixed;
+ .splitter {
+ height: 2px;
+ background: white;
+ border-top: 1px solid #D5D5D5;
+ border-bottom: 1px solid #D5D5D5;
+ }
+ }
+
+ .leftEmptyCell {
+ width: 50px;
+ }
+
+ .rightEmptyCell {
+ border-left: 1px solid #D5D5D5;
+ }
+
+ .day-separator {
+ position: absolute;
+ top: 0;
+ width: 3px;
+ height: 100%;
+ border-right: 1px solid #DDD;
+ border-left: 1px solid #DDD;
+ }
+}
+
+.gwt-cal .gwt-appointment-panel {
+ position: relative;
+ height: 1200px;
+}
+
+.gwt-cal .gwt-appointment-cell {
+ width: 99%;
+}
+
+.gwt-cal .dv-appointment {
+ border-style: solid;
+ border-width: 1px;
+ border-color: #979797 ! important;
+ position: absolute;
+ font-size: 10px;
+ font-family: arial;
+ overflow: hidden;
+ filter: alpha(opacity=50);
+ opacity: .9;
+ -moz-opacity: .9;
+}
+
+.gwt-cal .dv-appointment-selected {
+ border-style: solid;
+ border-width: 1px;
+ border-color: #979797 ! important;
+ position: absolute;
+ font-size: 10px;
+ font-family: arial;
+ overflow: hidden;
+ -webkit-box-shadow: 3px 3px 5px #B7B7B7;
+ -moz-box-shadow: 3px 3px 5px #B7B7B7;
+ opacity: 1;
+ -moz-opacity: 1;
+}
+
+.gwt-cal .dv-appointment-multiday {
+ border-style: solid;
+ border-width: 1px;
+ position: absolute;
+ font-size: 10px;
+ font-family: arial;
+ overflow: hidden;
+ filter: alpha(opacity=50);
+ opacity: .5;
+ -moz-opacity: .5;
+}
+
+.gwt-cal .dv-appointment-multiday-selected {
+ border-style: solid;
+ border-width: 1px;
+ position: absolute;
+ font-size: 10px;
+ font-family: arial;
+ overflow: hidden;
+ -webkit-box-shadow: 0 2px 2px -2px #CCC;
+ -moz-box-shadow: 0 2px 2px -2px #CCC;
+ opacity: 1;
+ -moz-opacity: 1;
+}
+
+.gwt-cal .dv-appointment .header , .gwt-cal .dv-appointment-selected .header {
+ white-space: initial;
+ color: #1D1D1D ! important;
+ padding: 1px;
+ padding-left: 8px;
+ font-weight: bold;
+ border-bottom: 1px solid black;
+}
+
+.gwt-cal .dv-appointment-multiday .header , .gwt-cal .dv-appointment-multiday-selected .header {
+ background: transparent;
+}
+
+.gwt-cal .dv-appointment .body , .gwt-cal .dv-appointment-selected .body , .gwt-cal .dv-appointment-multiday .body , .gwt-cal .dv-appointment-multiday-selected .body {
+ padding-left: 8px;
+ padding-top: 1px;
+ white-space: initial;
+ color: #1D1D1D ! important;
+}
+
+.gwt-cal .dv-appointment .footer , .gwt-cal .dv-appointment-selected .footer {
+ border-bottom: 3px double #FFF;
+ position: absolute;
+ bottom: 2px;
+ left: 48%;
+ height: 4px;
+ width: 8px;
+}
+
+.gwt-cal .dv-appointment-multiday .footer , .gwt-cal .dv-appointment-multiday-selected .footer {
+ display: none;
+}
+
+.gwt-cal-ListView {
+ overflow-y: scroll;
+ font-family: Verdana, Sans-serif;
+ font-size: 11px;
+ font-weight: bold;
+}
+
+.gwt-cal-ListView .dateCell {
+ white-space: nowrap;
+ padding-right: 30px;
+ color: #112ABB;
+ color: #3366CC;
+ font-family: Verdana, Sans-serif;
+ font-size: 11px;
+ font-weight: bold;
+}
+
+.gwt-cal-ListView .timeCell {
+ white-space: nowrap;
+ padding-left: 30px;
+ padding-right: 30px;
+ color: #3366CC;
+ font-family: Verdana, Sans-serif;
+ font-size: 11px;
+ font-weight: bold;
+}
+
+.gwt-cal-ListView .titleCell {
+ width: 99%;
+ color: #3366CC;
+ font-family: Verdana, Sans-serif;
+ font-size: 11px;
+ font-weight: bold;
+}
+
+.gwt-cal-ListView .row {
+ background: #E8EEF7;
+}
+
+.gwt-cal-ListView .row-alt {
+ background: #FFF;
+}
+
+.gwt-cal-ListView .row-today , .gwt-cal-ListView .row-alt-today {
+ background: #FFFFCC;
+}
+
+.gwt-cal-ListView .row-today .dateCell , .gwt-cal-ListView .row-alt-today .dateCell {
+ background: #BBCCDD;
+ border-color: #8899AA #DDEEFF #DDEEFF #8899AA;
+ border-style: solid;
+ border-width: 1px 1px 0 0;
+ color: #112ABB;
+}
+
+.gwt-cal-ListView .descriptionLabel {
+ color: #333333;
+ font-weight: normal;
+ font-size: 11px;
+}
+
+.gwt-cal-ListView .detailDecorator {
+ border-left: 2px solid #BBCCDC;
+ margin: 3px 0 5px 20px;
+ padding: 0 8px 4px 10px;
+}
+
+.gwt-cal-ListView .detailContainer {
+ font-weight: normal;
+ color: #333;
+}
+
+.gwt-cal-ListView .detailHeader {
+ font-weight: bold;
+}
+
+.gwt-cal-ListView .moreDetailsButton {
+ border-top: 1px solid #2952A3;
+ padding-top: 0.25em;
+ margin-top: 0.5em;
+ font-weight: normal;
+ font-color: #112ABB;
+ text-decoration: underline;
+}
+
+.gwt-cal-MonthView {
+ position: relative;
+ height: 523px;
+ white-space: nowrap;
+ overflow: hidden;
+ width: 100%;
+ overflow: hidden;
+}
+
+.gwt-cal-MonthView .grid {
+ table-layout: fixed;
+ background: #FFF;
+ width: 100%;
+ height: 100%;
+ border-collapse: collapse;
+}
+
+.gwt-cal-MonthView .canvas {
+ white-space: nowrap;
+ overflow: hidden;
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ width: 100%;
+ height: 100%;
+ font-family: arial;
+ font-size: 12px;
+ background: transparent;
+}
+
+.gwt-cal-MonthView .dayCell , .gwt-cal-MonthView .dayCell-today {
+ border-left: 1px solid rgb(195, 217, 255);
+ border-bottom: 1px solid rgb(195, 217, 255);
+}
+
+.gwt-cal-MonthView .weekDayLabel , .gwt-cal-MonthView .weekDayLabel-today {
+ height: 20px;
+ text-align: center;
+ background-color: rgb(195, 217, 255);
+ font-size: 13px;
+ color: rgb(17, 42, 187);
+}
+
+.gwt-Cal-MonthView .dayCellLabel , .gwt-Cal-MonthView .dayCellLabel-today {
+ background-color: rgb(232, 238, 247);
+ display: block;
+ height: 15px;
+ font-family: arial;
+ font-size: 11px;
+ text-align: right;
+ padding-right: 5px;
+}
+
+.gwt-Cal-MonthView .dayCellLabel-today-disabled , .gwt-Cal-MonthView .dayCellLabel-disabled {
+ background-color: rgb(238, 238, 238);
+ display: block;
+ height: 15px;
+ font-family: arial;
+ font-size: 11px;
+ text-align: right;
+ padding-right: 5px;
+ color: #999;
+}
+
+.gwt-Cal-MonthView .dayCellLabel-today , .gwt-Cal-MonthView .dayCellLabel-today-disabled {
+ background-color: rgb(187, 204, 221);
+}
+
+.gwt-cal-MonthView .dayCell-today {
+ background-color: rgb(255, 255, 204);
+}
+
+.gwt-cal-MonthView .moreAppointments {
+ position: absolute;
+ text-align: center;
+ text-decoration: underline;
+}
+
+.gwt-cal-MonthView .appointment {
+ position: absolute;
+ height: 20px;
+ overflow: hidden;
+ text-decoration: none;
+}
+
+.gwt-cal-MonthView .appointment-selected {
+ text-decoration: underline;
+ border: 0px;
+ overflow: hidden;
+}
+
+.gwt-cal-MonthView .appointment-multiday {
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ position: absolute;
+ height: 20px;
+ color: #FFF;
+ overflow: hidden;
+ border-style: solid;
+ border-width: 1px;
+}
+
+.gwt-cal-MonthView .appointment-multiday-selected {
+ position: absolute;
+ height: 20px;
+ overflow: hidden;
+ border-style: solid;
+ border-width: 1px;
+ -webkit-box-shadow: 3px 3px 5px #B7B7B7;
+ -moz-box-shadow: 3px 3px 5px #B7B7B7;
+ text-decoration: none;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ position: absolute;
+ height: 20px;
+ color: #FFF;
+ overflow: hidden;
+}
+
diff --git a/bootstrap/less/dropdowns.less b/bootstrap/less/dropdowns.less
new file mode 100644
index 0000000..390c374
--- /dev/null
+++ b/bootstrap/less/dropdowns.less
@@ -0,0 +1,210 @@
+//
+// Dropdown menus
+// --------------------------------------------------
+
+
+// Use the .menu class on any element within the topbar or ul.tabs and you'll get some superfancy dropdowns
+.dropup,
+.dropdown {
+ position: relative;
+}
+.dropdown-toggle {
+ // The caret makes the toggle a bit too tall in IE7
+ *margin-bottom: -3px;
+}
+.dropdown-toggle:active,
+.open .dropdown-toggle {
+ outline: 0;
+}
+
+// Dropdown arrow/caret
+// --------------------
+.caret {
+ display: inline-block;
+ width: 0;
+ height: 0;
+ vertical-align: top;
+ border-top: 4px solid @black;
+ border-right: 4px solid transparent;
+ border-left: 4px solid transparent;
+ content: "";
+}
+
+// Place the caret
+.dropdown .caret {
+ margin-top: 8px;
+ margin-left: 2px;
+}
+
+// The dropdown menu (ul)
+// ----------------------
+.dropdown-menu {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ z-index: @zindexDropdown;
+ display: none; // none by default, but block on "open" of the menu
+ float: left;
+ min-width: 160px;
+ padding: 5px 0;
+ margin: 2px 0 0; // override default ul
+ list-style: none;
+ background-color: @dropdownBackground;
+ border: 1px solid #ccc; // Fallback for IE7-8
+ border: 1px solid @dropdownBorder;
+ *border-right-width: 2px;
+ *border-bottom-width: 2px;
+ .border-radius(6px);
+ .box-shadow(0 5px 10px rgba(0,0,0,.2));
+ -webkit-background-clip: padding-box;
+ -moz-background-clip: padding;
+ background-clip: padding-box;
+
+ // Aligns the dropdown menu to right
+ &.pull-right {
+ right: 0;
+ left: auto;
+ }
+
+ // Dividers (basically an hr) within the dropdown
+ .divider {
+ .nav-divider(@dropdownDividerTop, @dropdownDividerBottom);
+ }
+
+ // Links within the dropdown menu
+ a {
+ display: block;
+ padding: 3px 20px;
+ clear: both;
+ font-weight: normal;
+ line-height: @baseLineHeight;
+ color: @dropdownLinkColor;
+ white-space: nowrap;
+ }
+}
+
+// Hover state
+// -----------
+.dropdown-menu li > a:hover,
+.dropdown-menu li > a:focus,
+.dropdown-submenu:hover > a {
+ text-decoration: none;
+ color: @dropdownLinkColorHover;
+ background-color: @dropdownLinkBackgroundHover;
+ #gradient > .vertical(@dropdownLinkBackgroundHover, darken(@dropdownLinkBackgroundHover, 5%));
+}
+
+// Active state
+// ------------
+.dropdown-menu .active > a,
+.dropdown-menu .active > a:hover {
+ color: @dropdownLinkColorHover;
+ text-decoration: none;
+ outline: 0;
+ background-color: @dropdownLinkBackgroundActive;
+ #gradient > .vertical(@dropdownLinkBackgroundActive, darken(@dropdownLinkBackgroundActive, 5%));
+}
+
+// Disabled state
+// --------------
+// Gray out text and ensure the hover state remains gray
+.dropdown-menu .disabled > a,
+.dropdown-menu .disabled > a:hover {
+ color: @grayLight;
+}
+// Nuke hover effects
+.dropdown-menu .disabled > a:hover {
+ text-decoration: none;
+ background-color: transparent;
+ cursor: default;
+}
+
+// Open state for the dropdown
+// ---------------------------
+.open {
+ // IE7's z-index only goes to the nearest positioned ancestor, which would
+ // make the menu appear below buttons that appeared later on the page
+ *z-index: @zindexDropdown;
+
+ & > .dropdown-menu {
+ display: block;
+ }
+}
+
+// Right aligned dropdowns
+// ---------------------------
+.pull-right > .dropdown-menu {
+ right: 0;
+ left: auto;
+}
+
+// Allow for dropdowns to go bottom up (aka, dropup-menu)
+// ------------------------------------------------------
+// Just add .dropup after the standard .dropdown class and you're set, bro.
+// TODO: abstract this so that the navbar fixed styles are not placed here?
+.dropup,
+.navbar-fixed-bottom .dropdown {
+ // Reverse the caret
+ .caret {
+ border-top: 0;
+ border-bottom: 4px solid @black;
+ content: "";
+ }
+ // Different positioning for bottom up menu
+ .dropdown-menu {
+ top: auto;
+ bottom: 100%;
+ margin-bottom: 1px;
+ }
+}
+
+// Sub menus
+// ---------------------------
+.dropdown-submenu {
+ position: relative;
+}
+.dropdown-submenu > .dropdown-menu {
+ top: 0;
+ left: 100%;
+ margin-top: -6px;
+ margin-left: -1px;
+ -webkit-border-radius: 0 6px 6px 6px;
+ -moz-border-radius: 0 6px 6px 6px;
+ border-radius: 0 6px 6px 6px;
+}
+.dropdown-submenu:hover > .dropdown-menu {
+ display: block;
+}
+
+.dropdown-submenu > a:after {
+ display: block;
+ content: " ";
+ float: right;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+ border-width: 5px 0 5px 5px;
+ border-left-color: darken(@dropdownBackground, 20%);
+ margin-top: 5px;
+ margin-right: -10px;
+}
+.dropdown-submenu:hover > a:after {
+ border-left-color: @dropdownLinkColorHover;
+}
+
+
+// Tweak nav headers
+// -----------------
+// Increase padding from 15px to 20px on sides
+.dropdown .dropdown-menu .nav-header {
+ padding-left: 20px;
+ padding-right: 20px;
+}
+
+// Typeahead
+// ---------
+.typeahead {
+ margin-top: 2px; // give it some space to breathe
+ .border-radius(4px);
+}
diff --git a/bootstrap/less/forms.less b/bootstrap/less/forms.less
new file mode 100644
index 0000000..2203f42
--- /dev/null
+++ b/bootstrap/less/forms.less
@@ -0,0 +1,653 @@
+//
+// Forms
+// --------------------------------------------------
+
+
+// GENERAL STYLES
+// --------------
+
+// Make all forms have space below them
+form {
+ margin: 0 0 @baseLineHeight;
+}
+
+fieldset {
+ padding: 0;
+ margin: 0;
+ border: 0;
+}
+
+// Groups of fields with labels on top (legends)
+legend {
+ display: block;
+ width: 100%;
+ padding: 0;
+ margin-bottom: @baseLineHeight;
+ font-size: @baseFontSize * 1.5;
+ line-height: @baseLineHeight * 2;
+ color: @grayDark;
+ border: 0;
+ border-bottom: 1px solid #e5e5e5;
+
+ // Small
+ small {
+ font-size: @baseLineHeight * .75;
+ color: @grayLight;
+ }
+}
+
+// Set font for forms
+label,
+input,
+button,
+select,
+textarea {
+ #font > .shorthand(@baseFontSize,normal,@baseLineHeight); // Set size, weight, line-height here
+}
+input,
+button,
+select,
+textarea {
+ font-family: @baseFontFamily; // And only set font-family here for those that need it (note the missing label element)
+}
+
+// Identify controls by their labels
+label {
+ display: block;
+ margin-bottom: 5px;
+}
+
+// Form controls
+// -------------------------
+
+// Shared size and type resets
+select,
+textarea,
+input[type="text"],
+input[type="password"],
+input[type="datetime"],
+input[type="datetime-local"],
+input[type="date"],
+input[type="month"],
+input[type="time"],
+input[type="week"],
+input[type="number"],
+input[type="email"],
+input[type="url"],
+input[type="search"],
+input[type="tel"],
+input[type="color"],
+.uneditable-input {
+ display: inline-block;
+ height: @baseLineHeight;
+ padding: 4px 6px;
+ margin-bottom: 9px;
+ font-size: @baseFontSize;
+ line-height: @baseLineHeight;
+ color: @gray;
+}
+
+// Reset appearance properties for textual inputs and textarea
+// Declare width for legacy (can't be on input[type=*] selectors or it's too specific)
+input,
+textarea,
+.uneditable-input {
+ width: 206px; // plus 12px padding and 2px border
+}
+// Reset height since textareas have rows
+textarea {
+ height: auto;
+}
+// Everything else
+textarea,
+input[type="text"],
+input[type="password"],
+input[type="datetime"],
+input[type="datetime-local"],
+input[type="date"],
+input[type="month"],
+input[type="time"],
+input[type="week"],
+input[type="number"],
+input[type="email"],
+input[type="url"],
+input[type="search"],
+input[type="tel"],
+input[type="color"],
+.uneditable-input {
+ background-color: @inputBackground;
+ border: 1px solid @inputBorder;
+ .box-shadow(0 1px 0 @white);
+ .transition(~"border linear .2s, box-shadow linear .2s");
+
+ // Focus state
+ &:focus {
+ border-color: rgba(82,168,236,.8);
+ outline: 0;
+ outline: thin dotted \9; /* IE6-9 */
+ .box-shadow(inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6));
+ }
+}
+
+// Position radios and checkboxes better
+input[type="radio"],
+input[type="checkbox"] {
+ margin: 4px 0 0;
+ *margin-top: 0; /* IE7 */
+ margin-top: 1px \9; /* IE8-9 */
+ line-height: normal;
+ cursor: pointer;
+}
+
+// Reset width of input images, buttons, radios, checkboxes
+input[type="file"],
+input[type="image"],
+input[type="submit"],
+input[type="reset"],
+input[type="button"],
+input[type="radio"],
+input[type="checkbox"] {
+ width: auto; // Override of generic input selector
+}
+
+// Set the height of select and file controls to match text inputs
+select,
+input[type="file"] {
+ height: 26px; /* In IE7, the height of the select element cannot be changed by height, only font-size */
+ *margin-top: 4px; /* For IE7, add top margin to align select with labels */
+ line-height: 26px;
+}
+
+// Make select elements obey height by applying a border
+select {
+ width: 220px; // default input width + 10px of padding that doesn't get applied
+ border: 1px solid @inputBorder;
+ background-color: @inputBackground; // Chrome on Linux and Mobile Safari need background-color
+
+ #gradient > .vertical(#FCFCFC, #F1F1F1);
+ color: gray;
+ border-radius: 2px;
+}
+
+// Make multiple select elements height not fixed
+select[multiple],
+select[size] {
+ height: auto;
+}
+
+// Focus for select, file, radio, and checkbox
+select:focus,
+input[type="file"]:focus,
+input[type="radio"]:focus,
+input[type="checkbox"]:focus {
+ .tab-focus();
+}
+
+
+// Uneditable inputs
+// -------------------------
+
+// Make uneditable inputs look inactive
+.uneditable-input,
+.uneditable-textarea {
+ color: @grayLight;
+ background-color: darken(@inputBackground, 1%);
+ border-color: @inputBorder;
+ .box-shadow(inset 0 1px 2px rgba(0,0,0,.025));
+ cursor: not-allowed;
+}
+
+// For text that needs to appear as an input but should not be an input
+.uneditable-input {
+ overflow: hidden; // prevent text from wrapping, but still cut it off like an input does
+ white-space: nowrap;
+}
+
+// Make uneditable textareas behave like a textarea
+.uneditable-textarea {
+ width: auto;
+ height: auto;
+}
+
+
+// Placeholder
+// -------------------------
+
+// Placeholder text gets special styles because when browsers invalidate entire lines if it doesn���������t understand a selector
+input,
+textarea {
+ .placeholder();
+}
+
+
+// CHECKBOXES & RADIOS
+// -------------------
+
+// Indent the labels to position radios/checkboxes as hanging
+.radio,
+.checkbox {
+ min-height: 18px; // clear the floating input if there is no label text
+ padding-left: 18px;
+}
+.radio input[type="radio"],
+.checkbox input[type="checkbox"] {
+ float: left;
+ margin-left: -18px;
+}
+
+// Move the options list down to align with labels
+.controls > .radio:first-child,
+.controls > .checkbox:first-child {
+ padding-top: 5px; // has to be padding because margin collaspes
+}
+
+// Radios and checkboxes on same line
+// TODO v3: Convert .inline to .control-inline
+.radio.inline,
+.checkbox.inline {
+ display: inline-block;
+ padding-top: 5px;
+ margin-bottom: 0;
+ vertical-align: middle;
+}
+.radio.inline + .radio.inline,
+.checkbox.inline + .checkbox.inline {
+ margin-left: 10px; // space out consecutive inline controls
+}
+
+
+
+// INPUT SIZES
+// -----------
+
+// General classes for quick sizes
+.input-mini { width: 60px; }
+.input-small { width: 90px; }
+.input-medium { width: 150px; }
+.input-large { width: 210px; }
+.input-xlarge { width: 270px; }
+.input-xxlarge { width: 530px; }
+
+// Grid style input sizes
+input[class*="span"],
+select[class*="span"],
+textarea[class*="span"],
+.uneditable-input[class*="span"],
+// Redeclare since the fluid row class is more specific
+.row-fluid input[class*="span"],
+.row-fluid select[class*="span"],
+.row-fluid textarea[class*="span"],
+.row-fluid .uneditable-input[class*="span"] {
+ float: none;
+ margin-left: 0;
+}
+// Ensure input-prepend/append never wraps
+.input-append input[class*="span"],
+.input-append .uneditable-input[class*="span"],
+.input-prepend input[class*="span"],
+.input-prepend .uneditable-input[class*="span"],
+.row-fluid input[class*="span"],
+.row-fluid select[class*="span"],
+.row-fluid textarea[class*="span"],
+.row-fluid .uneditable-input[class*="span"],
+.row-fluid .input-prepend [class*="span"],
+.row-fluid .input-append [class*="span"] {
+ display: inline-block;
+}
+
+
+
+// GRID SIZING FOR INPUTS
+// ----------------------
+
+// Grid sizes
+#grid > .input(@gridColumnWidth, @gridGutterWidth);
+
+// Control row for multiple inputs per line
+.controls-row {
+ .clearfix(); // Clear the float from controls
+}
+.controls-row [class*="span"] {
+ float: left; // Float to collapse white-space for proper grid alignment
+}
+
+
+
+
+// DISABLED STATE
+// --------------
+
+// Disabled and read-only inputs
+input[disabled],
+select[disabled],
+textarea[disabled],
+input[readonly],
+select[readonly],
+textarea[readonly] {
+ cursor: not-allowed;
+ background-color: @inputDisabledBackground;
+}
+// Explicitly reset the colors here
+input[type="radio"][disabled],
+input[type="checkbox"][disabled],
+input[type="radio"][readonly],
+input[type="checkbox"][readonly] {
+ background-color: transparent;
+}
+
+
+
+
+// FORM FIELD FEEDBACK STATES
+// --------------------------
+
+// Warning
+.control-group.warning {
+ .formFieldState(@warningText, @warningText, @warningBackground);
+}
+// Error
+.control-group.error {
+ .formFieldState(@errorText, @errorText, @errorBackground);
+}
+// Success
+.control-group.success {
+ .formFieldState(@successText, @successText, @successBackground);
+}
+// Success
+.control-group.info {
+ .formFieldState(@infoText, @infoText, @infoBackground);
+}
+
+// HTML5 invalid states
+// Shares styles with the .control-group.error above
+input:focus:required:invalid,
+textarea:focus:required:invalid,
+select:focus:required:invalid {
+ color: #b94a48;
+ border-color: #ee5f5b;
+ &:focus {
+ border-color: darken(#ee5f5b, 10%);
+ .box-shadow(0 0 6px lighten(#ee5f5b, 20%));
+ }
+}
+
+
+
+// FORM ACTIONS
+// ------------
+
+.form-actions {
+ padding: (@baseLineHeight - 1) 20px @baseLineHeight;
+ margin-top: @baseLineHeight;
+ margin-bottom: @baseLineHeight;
+ background-color: @formActionsBackground;
+ border-top: 1px solid #e5e5e5;
+ .clearfix(); // Adding clearfix to allow for .pull-right button containers
+}
+
+
+
+// HELP TEXT
+// ---------
+
+.help-block,
+.help-inline {
+ color: lighten(@textColor, 15%); // lighten the text some for contrast
+}
+
+.help-block {
+ display: block; // account for any element using help-block
+ margin-bottom: @baseLineHeight / 2;
+}
+
+.help-inline {
+ display: inline-block;
+ .ie7-inline-block();
+ vertical-align: middle;
+ padding-left: 5px;
+}
+
+
+
+// INPUT GROUPS
+// ------------
+
+// Allow us to put symbols and text within the input field for a cleaner look
+.input-append,
+.input-prepend {
+ margin-bottom: 5px;
+ font-size: 0;
+ white-space: nowrap; // Prevent span and input from separating
+
+ input,
+ select,
+ .uneditable-input {
+ position: relative; // placed here by default so that on :focus we can place the input above the .add-on for full border and box-shadow goodness
+ margin-bottom: 0; // prevent bottom margin from screwing up alignment in stacked forms
+ *margin-left: 0;
+ font-size: @baseFontSize;
+ vertical-align: top;
+ .border-radius(0 @inputBorderRadius @inputBorderRadius 0);
+ // Make input on top when focused so blue border and shadow always show
+ &:focus {
+ z-index: 2;
+ }
+ }
+ .add-on {
+ display: inline-block;
+ width: auto;
+ height: @baseLineHeight;
+ min-width: 16px;
+ padding: 4px 5px;
+ font-size: @baseFontSize;
+ font-weight: normal;
+ line-height: @baseLineHeight;
+ text-align: center;
+ text-shadow: 0 1px 0 @white;
+ background-color: @grayLighter;
+ border: 1px solid #ccc;
+ }
+ .add-on,
+ .btn {
+ vertical-align: top;
+ .border-radius(0);
+ }
+ .active {
+ background-color: lighten(@green, 30);
+ border-color: @green;
+ }
+}
+.input-prepend {
+ .add-on,
+ .btn {
+ margin-right: -1px;
+ }
+ .add-on:first-child,
+ .btn:first-child {
+ .border-radius(@inputBorderRadius 0 0 @inputBorderRadius);
+ }
+}
+.input-append {
+ input,
+ select,
+ .uneditable-input {
+ .border-radius(@inputBorderRadius 0 0 @inputBorderRadius);
+ }
+ .add-on,
+ .btn {
+ margin-left: -1px;
+ }
+ .add-on:last-child,
+ .btn:last-child {
+ .border-radius(0 @inputBorderRadius @inputBorderRadius 0);
+ }
+}
+// Remove all border-radius for inputs with both prepend and append
+.input-prepend.input-append {
+ input,
+ select,
+ .uneditable-input {
+ .border-radius(0);
+ }
+ .add-on:first-child,
+ .btn:first-child {
+ margin-right: -1px;
+ .border-radius(@inputBorderRadius 0 0 @inputBorderRadius);
+ }
+ .add-on:last-child,
+ .btn:last-child {
+ margin-left: -1px;
+ .border-radius(0 @inputBorderRadius @inputBorderRadius 0);
+ }
+}
+
+
+
+// SEARCH FORM
+// -----------
+
+input.search-query {
+ padding-right: 14px;
+ padding-right: 4px \9;
+ padding-left: 14px;
+ padding-left: 4px \9; /* IE7-8 doesn't have border-radius, so don't indent the padding */
+ margin-bottom: 0; // Remove the default margin on all inputs
+ .border-radius(15px);
+}
+
+/* Allow for input prepend/append in search forms */
+.form-search .input-append .search-query,
+.form-search .input-prepend .search-query {
+ .border-radius(0); // Override due to specificity
+}
+.form-search .input-append .search-query {
+ .border-radius(14px 0 0 14px);
+}
+.form-search .input-append .btn {
+ .border-radius(0 14px 14px 0);
+}
+.form-search .input-prepend .search-query {
+ .border-radius(0 14px 14px 0);
+}
+.form-search .input-prepend .btn {
+ .border-radius(14px 0 0 14px);
+}
+
+
+
+
+// HORIZONTAL & VERTICAL FORMS
+// ---------------------------
+
+// Common properties
+// -----------------
+
+.form-search,
+.form-inline,
+.form-horizontal {
+ input,
+ textarea,
+ select,
+ .help-inline,
+ .uneditable-input,
+ .input-prepend,
+ .input-append {
+ display: inline-block;
+ .ie7-inline-block();
+ margin-bottom: 0;
+ vertical-align: middle;
+ }
+ // Re-hide hidden elements due to specifity
+ .hide {
+ display: none;
+ }
+}
+.form-search label,
+.form-inline label,
+.form-search .btn-group,
+.form-inline .btn-group {
+ display: inline-block;
+}
+// Remove margin for input-prepend/-append
+.form-search .input-append,
+.form-inline .input-append,
+.form-search .input-prepend,
+.form-inline .input-prepend {
+ margin-bottom: 0;
+}
+// Inline checkbox/radio labels (remove padding on left)
+.form-search .radio,
+.form-search .checkbox,
+.form-inline .radio,
+.form-inline .checkbox {
+ padding-left: 0;
+ margin-bottom: 0;
+ vertical-align: middle;
+}
+// Remove float and margin, set to inline-block
+.form-search .radio input[type="radio"],
+.form-search .checkbox input[type="checkbox"],
+.form-inline .radio input[type="radio"],
+.form-inline .checkbox input[type="checkbox"] {
+ float: left;
+ margin-right: 3px;
+ margin-left: 0;
+}
+
+
+// Margin to space out fieldsets
+.control-group {
+ margin-bottom: @baseLineHeight / 2;
+}
+
+// Legend collapses margin, so next element is responsible for spacing
+legend + .control-group {
+ margin-top: @baseLineHeight;
+ -webkit-margin-top-collapse: separate;
+}
+
+// Horizontal-specific styles
+// --------------------------
+
+.form-horizontal {
+ // Increase spacing between groups
+ .control-group {
+ margin-bottom: @baseLineHeight;
+ .clearfix();
+ }
+ // Float the labels left
+ .control-label {
+ float: left;
+ width: @horizontalComponentOffset - 20;
+ padding-top: 5px;
+ text-align: right;
+ }
+ // Move over all input controls and content
+ .controls {
+ // Super jank IE7 fix to ensure the inputs in .input-append and input-prepend
+ // don't inherit the margin of the parent, in this case .controls
+ *display: inline-block;
+ *padding-left: 20px;
+ margin-left: @horizontalComponentOffset;
+ *margin-left: 0;
+ &:first-child {
+ *padding-left: @horizontalComponentOffset;
+ }
+ }
+ // Remove bottom margin on block level help text since that's accounted for on .control-group
+ .help-block {
+ margin-bottom: 0;
+ }
+ // And apply it only to .help-block instances that follow a form control
+ input,
+ select,
+ textarea {
+ + .help-block {
+ margin-top: @baseLineHeight / 2;
+ }
+ }
+ // Move over buttons in .form-actions to align with .controls
+ .form-actions {
+ padding-left: @horizontalComponentOffset;
+ }
+}
diff --git a/bootstrap/less/grid.less b/bootstrap/less/grid.less
new file mode 100644
index 0000000..750d203
--- /dev/null
+++ b/bootstrap/less/grid.less
@@ -0,0 +1,21 @@
+//
+// Grid system
+// --------------------------------------------------
+
+
+// Fixed (940px)
+#grid > .core(@gridColumnWidth, @gridGutterWidth);
+
+// Fluid (940px)
+#grid > .fluid(@fluidGridColumnWidth, @fluidGridGutterWidth);
+
+// Reset utility classes due to specificity
+[class*="span"].hide,
+.row-fluid [class*="span"].hide {
+ display: none;
+}
+
+[class*="span"].pull-right,
+.row-fluid [class*="span"].pull-right {
+ float: right;
+}
diff --git a/bootstrap/less/hero-unit.less b/bootstrap/less/hero-unit.less
new file mode 100644
index 0000000..672b7d7
--- /dev/null
+++ b/bootstrap/less/hero-unit.less
@@ -0,0 +1,24 @@
+//
+// Hero unit
+// --------------------------------------------------
+
+
+.hero-unit {
+ padding: 60px;
+ margin-bottom: 30px;
+ background-color: @heroUnitBackground;
+ .border-radius(6px);
+ h1 {
+ margin-bottom: 0;
+ font-size: 60px;
+ line-height: 1;
+ color: @heroUnitHeadingColor;
+ letter-spacing: -1px;
+ }
+ p {
+ font-size: 18px;
+ font-weight: 200;
+ line-height: @baseLineHeight * 1.5;
+ color: @heroUnitLeadColor;
+ }
+}
diff --git a/bootstrap/less/labels-badges.less b/bootstrap/less/labels-badges.less
new file mode 100644
index 0000000..2f15fec
--- /dev/null
+++ b/bootstrap/less/labels-badges.less
@@ -0,0 +1,72 @@
+//
+// Labels and badges
+// --------------------------------------------------
+
+
+// Base classes
+.label,
+.badge {
+ font-size: @baseFontSize * .846;
+ font-weight: bold;
+ line-height: 14px; // ensure proper line-height if floated
+ color: @white;
+ vertical-align: baseline;
+ white-space: nowrap;
+ text-shadow: 0 -1px 0 rgba(0,0,0,.25);
+ background-color: @grayLight;
+}
+// Set unique padding and border-radii
+.label {
+ padding: 1px 4px 2px;
+ .border-radius(3px);
+}
+.badge {
+ padding: 1px 9px 2px;
+ .border-radius(9px);
+}
+
+// Hover state, but only for links
+a {
+ &.label:hover,
+ &.badge:hover {
+ color: @white;
+ text-decoration: none;
+ cursor: pointer;
+ }
+}
+
+// Colors
+// Only give background-color difference to links (and to simplify, we don't qualifty with `a` but [href] attribute)
+.label,
+.badge {
+ // Important (red)
+ &-important { background-color: @errorText; }
+ &-important[href] { background-color: darken(@errorText, 10%); }
+ // Warnings (orange)
+ &-warning { background-color: @orange; }
+ &-warning[href] { background-color: darken(@orange, 10%); }
+ // Success (green)
+ &-success { background-color: @successText; }
+ &-success[href] { background-color: darken(@successText, 10%); }
+ // Info (turquoise)
+ &-info { background-color: @infoText; }
+ &-info[href] { background-color: darken(@infoText, 10%); }
+ // Inverse (black)
+ &-inverse { background-color: @grayDark; }
+ &-inverse[href] { background-color: darken(@grayDark, 10%); }
+}
+
+// Quick fix for labels/badges in buttons
+.btn {
+ .label,
+ .badge {
+ position: relative;
+ top: -1px;
+ }
+}
+.btn-mini {
+ .label,
+ .badge {
+ top: 0;
+ }
+}
diff --git a/bootstrap/less/layouts.less b/bootstrap/less/layouts.less
new file mode 100644
index 0000000..24a2062
--- /dev/null
+++ b/bootstrap/less/layouts.less
@@ -0,0 +1,16 @@
+//
+// Layouts
+// --------------------------------------------------
+
+
+// Container (centered, fixed-width layouts)
+.container {
+ .container-fixed();
+}
+
+// Fluid layouts (left aligned, with sidebar, min- & max-width content)
+.container-fluid {
+ padding-right: @gridGutterWidth;
+ padding-left: @gridGutterWidth;
+ .clearfix();
+}
\ No newline at end of file
diff --git a/bootstrap/less/mixins.less b/bootstrap/less/mixins.less
new file mode 100644
index 0000000..67f1c0a
--- /dev/null
+++ b/bootstrap/less/mixins.less
@@ -0,0 +1,681 @@
+//
+// Mixins
+// --------------------------------------------------
+
+
+// UTILITY MIXINS
+// --------------------------------------------------
+
+// Clearfix
+// --------
+// For clearing floats like a boss h5bp.com/q
+.clearfix {
+ *zoom: 1;
+ &:before,
+ &:after {
+ display: table;
+ content: "";
+ // Fixes Opera/contenteditable bug:
+ // http://nicolasgallagher.com/micro-clearfix-hack/#comment-36952
+ line-height: 0;
+ }
+ &:after {
+ clear: both;
+ }
+}
+
+// Webkit-style focus
+// ------------------
+.tab-focus() {
+ // Default
+ outline: thin dotted #333;
+ // Webkit
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+
+// Center-align a block level element
+// ----------------------------------
+.center-block() {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+// IE7 inline-block
+// ----------------
+.ie7-inline-block() {
+ *display: inline; /* IE7 inline-block hack */
+ *zoom: 1;
+}
+
+// IE7 likes to collapse whitespace on either side of the inline-block elements.
+// Ems because we're attempting to match the width of a space character. Left
+// version is for form buttons, which typically come after other elements, and
+// right version is for icons, which come before. Applying both is ok, but it will
+// mean that space between those elements will be .6em (~2 space characters) in IE7,
+// instead of the 1 space in other browsers.
+.ie7-restore-left-whitespace() {
+ *margin-left: .3em;
+
+ &:first-child {
+ *margin-left: 0;
+ }
+}
+
+.ie7-restore-right-whitespace() {
+ *margin-right: .3em;
+}
+
+// Sizing shortcuts
+// -------------------------
+.size(@height, @width) {
+ width: @width;
+ height: @height;
+}
+.square(@size) {
+ .size(@size, @size);
+}
+
+// Placeholder text
+// -------------------------
+.placeholder(@color: @placeholderText) {
+ &:-moz-placeholder {
+ color: @color;
+ }
+ &:-ms-input-placeholder {
+ color: @color;
+ }
+ &::-webkit-input-placeholder {
+ color: @color;
+ }
+}
+
+// Text overflow
+// -------------------------
+// Requires inline-block or block for proper styling
+.text-overflow() {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+// CSS image replacement
+// -------------------------
+// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757
+.hide-text {
+ font: 0/0 a;
+ color: transparent;
+ text-shadow: none;
+ background-color: transparent;
+ border: 0;
+}
+
+
+// FONTS
+// --------------------------------------------------
+
+#font {
+ #family {
+ .serif() {
+ font-family: @serifFontFamily;
+ }
+ .sans-serif() {
+ font-family: @sansFontFamily;
+ }
+ .monospace() {
+ font-family: @monoFontFamily;
+ }
+ }
+ .shorthand(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) {
+ font-size: @size;
+ font-weight: @weight;
+ line-height: @lineHeight;
+ }
+ .serif(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) {
+ #font > #family > .serif;
+ #font > .shorthand(@size, @weight, @lineHeight);
+ }
+ .sans-serif(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) {
+ #font > #family > .sans-serif;
+ #font > .shorthand(@size, @weight, @lineHeight);
+ }
+ .monospace(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) {
+ #font > #family > .monospace;
+ #font > .shorthand(@size, @weight, @lineHeight);
+ }
+}
+
+
+// FORMS
+// --------------------------------------------------
+
+// Block level inputs
+.input-block-level {
+ display: block;
+ width: 100%;
+ min-height: 30px; // Make inputs at least the height of their button counterpart
+ .box-sizing(border-box); // Makes inputs behave like true block-level elements
+}
+
+
+
+// Mixin for form field states
+.formFieldState(@textColor: #555, @borderColor: #ccc, @backgroundColor: #f5f5f5) {
+ // Set the text color
+ > label,
+ .help-block,
+ .help-inline {
+ color: @textColor;
+ }
+ // Style inputs accordingly
+ .checkbox,
+ .radio,
+ input,
+ select,
+ textarea {
+ color: @textColor;
+ }
+ input,
+ select,
+ textarea {
+ border-color: @borderColor;
+ .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work
+ &:focus {
+ border-color: darken(@borderColor, 10%);
+ .box-shadow(inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@borderColor, 20%));
+ }
+ }
+ // Give a small background color for input-prepend/-append
+ .input-prepend .add-on,
+ .input-append .add-on {
+ color: @textColor;
+ background-color: @backgroundColor;
+ border-color: @textColor;
+ }
+}
+
+
+
+// CSS3 PROPERTIES
+// --------------------------------------------------
+
+// Border Radius
+.border-radius(@radius) {
+ -webkit-border-radius: @radius;
+ -moz-border-radius: @radius;
+ border-radius: @radius;
+}
+
+// Single Corner Border Radius
+.border-top-left-radius(@radius) {
+ -webkit-border-top-left-radius: @radius;
+ -moz-border-radius-topleft: @radius;
+ border-top-left-radius: @radius;
+}
+.border-top-right-radius(@radius) {
+ -webkit-border-top-right-radius: @radius;
+ -moz-border-radius-topright: @radius;
+ border-top-right-radius: @radius;
+}
+.border-bottom-right-radius(@radius) {
+ -webkit-border-bottom-right-radius: @radius;
+ -moz-border-radius-bottomright: @radius;
+ border-bottom-right-radius: @radius;
+}
+.border-bottom-left-radius(@radius) {
+ -webkit-border-bottom-left-radius: @radius;
+ -moz-border-radius-bottomleft: @radius;
+ border-bottom-left-radius: @radius;
+}
+
+// Single Side Border Radius
+.border-top-radius(@radius) {
+ .border-top-right-radius(@radius);
+ .border-top-left-radius(@radius);
+}
+.border-right-radius(@radius) {
+ .border-top-right-radius(@radius);
+ .border-bottom-right-radius(@radius);
+}
+.border-bottom-radius(@radius) {
+ .border-bottom-right-radius(@radius);
+ .border-bottom-left-radius(@radius);
+}
+.border-left-radius(@radius) {
+ .border-top-left-radius(@radius);
+ .border-bottom-left-radius(@radius);
+}
+
+// Drop shadows
+.box-shadow(@shadowA, @shadowB:X, ...){
+ // Multiple shadow solution from http://toekneestuck.com/blog/2012/05/15/less-css-arguments-variable/
+ @props: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`;
+ -webkit-box-shadow: @props;
+ -moz-box-shadow: @props;
+ box-shadow: @props;
+}
+
+// Transitions
+.transition(@transition) {
+ -webkit-transition: @transition;
+ -moz-transition: @transition;
+ -o-transition: @transition;
+ transition: @transition;
+}
+.transition-delay(@transition-delay) {
+ -webkit-transition-delay: @transition-delay;
+ -moz-transition-delay: @transition-delay;
+ -o-transition-delay: @transition-delay;
+ transition-delay: @transition-delay;
+}
+
+// Transformations
+.rotate(@degrees) {
+ -webkit-transform: rotate(@degrees);
+ -moz-transform: rotate(@degrees);
+ -ms-transform: rotate(@degrees);
+ -o-transform: rotate(@degrees);
+ transform: rotate(@degrees);
+}
+.scale(@ratio) {
+ -webkit-transform: scale(@ratio);
+ -moz-transform: scale(@ratio);
+ -ms-transform: scale(@ratio);
+ -o-transform: scale(@ratio);
+ transform: scale(@ratio);
+}
+.translate(@x, @y) {
+ -webkit-transform: translate(@x, @y);
+ -moz-transform: translate(@x, @y);
+ -ms-transform: translate(@x, @y);
+ -o-transform: translate(@x, @y);
+ transform: translate(@x, @y);
+}
+.skew(@x, @y) {
+ -webkit-transform: skew(@x, @y);
+ -moz-transform: skew(@x, @y);
+ -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twitter/bootstrap/issues/4885
+ -o-transform: skew(@x, @y);
+ transform: skew(@x, @y);
+}
+.translate3d(@x, @y, @z) {
+ -webkit-transform: translate3d(@x, @y, @z);
+ -moz-transform: translate3d(@x, @y, @z);
+ -o-transform: translate3d(@x, @y, @z);
+ transform: translate3d(@x, @y, @z);
+}
+
+// Backface visibility
+// Prevent browsers from flickering when using CSS 3D transforms.
+// Default value is `visible`, but can be changed to `hidden
+// See git pull https://github.com/dannykeane/bootstrap.git backface-visibility for examples
+.backface-visibility(@visibility){
+ -webkit-backface-visibility: @visibility;
+ -moz-backface-visibility: @visibility;
+ backface-visibility: @visibility;
+}
+
+// Background clipping
+// Heads up: FF 3.6 and under need "padding" instead of "padding-box"
+.background-clip(@clip) {
+ -webkit-background-clip: @clip;
+ -moz-background-clip: @clip;
+ background-clip: @clip;
+}
+
+// Background sizing
+.background-size(@size){
+ -webkit-background-size: @size;
+ -moz-background-size: @size;
+ -o-background-size: @size;
+ background-size: @size;
+}
+
+
+// Box sizing
+.box-sizing(@boxmodel) {
+ -webkit-box-sizing: @boxmodel;
+ -moz-box-sizing: @boxmodel;
+ box-sizing: @boxmodel;
+}
+
+// User select
+// For selecting text on the page
+.user-select(@select) {
+ -webkit-user-select: @select;
+ -moz-user-select: @select;
+ -ms-user-select: @select;
+ -o-user-select: @select;
+ user-select: @select;
+}
+
+// Resize anything
+.resizable(@direction) {
+ resize: @direction; // Options: horizontal, vertical, both
+ overflow: auto; // Safari fix
+}
+
+// CSS3 Content Columns
+.content-columns(@columnCount, @columnGap: @gridGutterWidth) {
+ -webkit-column-count: @columnCount;
+ -moz-column-count: @columnCount;
+ column-count: @columnCount;
+ -webkit-column-gap: @columnGap;
+ -moz-column-gap: @columnGap;
+ column-gap: @columnGap;
+}
+
+// Optional hyphenation
+.hyphens(@mode: auto) {
+ word-wrap: break-word;
+ -webkit-hyphens: @mode;
+ -moz-hyphens: @mode;
+ -ms-hyphens: @mode;
+ -o-hyphens: @mode;
+ hyphens: @mode;
+}
+
+// Opacity
+.opacity(@opacity) {
+ opacity: @opacity / 100;
+ filter: ~"alpha(opacity=@{opacity})";
+}
+
+
+
+// BACKGROUNDS
+// --------------------------------------------------
+
+// Add an alphatransparency value to any background or border color (via Elyse Holladay)
+#translucent {
+ .background(@color: @white, @alpha: 1) {
+ background-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha);
+ }
+ .border(@color: @white, @alpha: 1) {
+ border-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha);
+ .background-clip(padding-box);
+ }
+}
+
+// Gradient Bar Colors for buttons and alerts
+.gradientBar(@primaryColor, @secondaryColor, @textColor: #fff, @textShadow: 0 -1px 0 rgba(0,0,0,.25)) {
+ color: @textColor;
+ text-shadow: @textShadow;
+ #gradient > .vertical(@primaryColor, @secondaryColor);
+ border-color: @secondaryColor @secondaryColor darken(@secondaryColor, 15%);
+ border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) fadein(rgba(0,0,0,.1), 15%);
+}
+
+// Gradients
+#gradient {
+ .horizontal(@startColor: #555, @endColor: #333) {
+ background-color: @endColor;
+ background-image: -moz-linear-gradient(left, @startColor, @endColor); // FF 3.6+
+ background-image: -webkit-gradient(linear, 0 0, 100% 0, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+
+ background-image: -webkit-linear-gradient(left, @startColor, @endColor); // Safari 5.1+, Chrome 10+
+ background-image: -o-linear-gradient(left, @startColor, @endColor); // Opera 11.10
+ background-image: linear-gradient(to right, @startColor, @endColor); // Standard, IE10
+ background-repeat: repeat-x;
+ filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",argb(@startColor),argb(@endColor))); // IE9 and down
+ }
+ .vertical(@startColor: #555, @endColor: #333) {
+ background-color: mix(@startColor, @endColor, 60%);
+ background-image: -moz-linear-gradient(top, @startColor, @endColor); // FF 3.6+
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+
+ background-image: -webkit-linear-gradient(top, @startColor, @endColor); // Safari 5.1+, Chrome 10+
+ background-image: -o-linear-gradient(top, @startColor, @endColor); // Opera 11.10
+ background-image: linear-gradient(to bottom, @startColor, @endColor); // Standard, IE10
+ background-repeat: repeat-x;
+ filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down
+ }
+ .directional(@startColor: #555, @endColor: #333, @deg: 45deg) {
+ background-color: @endColor;
+ background-repeat: repeat-x;
+ background-image: -moz-linear-gradient(@deg, @startColor, @endColor); // FF 3.6+
+ background-image: -webkit-linear-gradient(@deg, @startColor, @endColor); // Safari 5.1+, Chrome 10+
+ background-image: -o-linear-gradient(@deg, @startColor, @endColor); // Opera 11.10
+ background-image: linear-gradient(@deg, @startColor, @endColor); // Standard, IE10
+ }
+ .vertical-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) {
+ background-color: mix(@midColor, @endColor, 80%);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), color-stop(@colorStop, @midColor), to(@endColor));
+ background-image: -webkit-linear-gradient(@startColor, @midColor @colorStop, @endColor);
+ background-image: -moz-linear-gradient(top, @startColor, @midColor @colorStop, @endColor);
+ background-image: -o-linear-gradient(@startColor, @midColor @colorStop, @endColor);
+ background-image: linear-gradient(@startColor, @midColor @colorStop, @endColor);
+ background-repeat: no-repeat;
+ filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down, gets no color-stop at all for proper fallback
+ }
+ .radial(@innerColor: #555, @outerColor: #333) {
+ background-color: @outerColor;
+ background-image: -webkit-gradient(radial, center center, 0, center center, 460, from(@innerColor), to(@outerColor));
+ background-image: -webkit-radial-gradient(circle, @innerColor, @outerColor);
+ background-image: -moz-radial-gradient(circle, @innerColor, @outerColor);
+ background-image: -o-radial-gradient(circle, @innerColor, @outerColor);
+ background-repeat: no-repeat;
+ }
+ .striped(@color: #555, @angle: 45deg) {
+ background-color: @color;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255,255,255,.15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255,255,255,.15)), color-stop(.75, rgba(255,255,255,.15)), color-stop(.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent);
+ }
+}
+// Reset filters for IE
+.reset-filter() {
+ filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)"));
+}
+
+
+
+// COMPONENT MIXINS
+// --------------------------------------------------
+
+// Horizontal dividers
+// -------------------------
+// Dividers (basically an hr) within dropdowns and nav lists
+.nav-divider(@top: #e5e5e5, @bottom: @white) {
+ // IE7 needs a set width since we gave a height. Restricting just
+ // to IE7 to keep the 1px left/right space in other browsers.
+ // It is unclear where IE is getting the extra space that we need
+ // to negative-margin away, but so it goes.
+ *width: 100%;
+ height: 1px;
+ margin: ((@baseLineHeight / 2) - 1) 1px; // 8px 1px
+ *margin: -5px 0 5px;
+ overflow: hidden;
+ background-color: @top;
+ border-bottom: 1px solid @bottom;
+}
+
+// Button backgrounds
+// ------------------
+.buttonBackground(@startColor, @endColor, @textColor: #fff, @textShadow: 0 -1px 0 rgba(0,0,0,.25)) {
+ // gradientBar will set the background to a pleasing blend of these, to support IE<=9
+ .gradientBar(@startColor, @endColor, @textColor, @textShadow);
+ *background-color: @endColor; /* Darken IE7 buttons by default so they stand out more given they won't have borders */
+ .reset-filter();
+
+ // in these cases the gradient won't cover the background, so we override
+ &:hover, &:active, &.active, &.disabled, &[disabled] {
+ color: @textColor;
+ background-color: @endColor;
+ *background-color: darken(@endColor, 5%);
+ }
+
+ // IE 7 + 8 can't handle box-shadow to show active, so we darken a bit ourselves
+ &:active,
+ &.active {
+ background-color: darken(@endColor, 10%) e("\9");
+ }
+}
+
+// Navbar vertical align
+// -------------------------
+// Vertically center elements in the navbar.
+// Example: an element has a height of 30px, so write out `.navbarVerticalAlign(30px);` to calculate the appropriate top margin.
+.navbarVerticalAlign(@elementHeight) {
+ margin-top: (@navbarHeight - @elementHeight) / 2;
+}
+
+
+
+// Grid System
+// -----------
+
+// Centered container element
+.container-fixed() {
+ margin-right: auto;
+ margin-left: auto;
+ .clearfix();
+}
+
+// Table columns
+.tableColumns(@columnSpan: 1) {
+ float: none; // undo default grid column styles
+ width: ((@gridColumnWidth) * @columnSpan) + (@gridGutterWidth * (@columnSpan - 1)) - 16; // 16 is total padding on left and right of table cells
+ margin-left: 0; // undo default grid column styles
+}
+
+// Make a Grid
+// Use .makeRow and .makeColumn to assign semantic layouts grid system behavior
+.makeRow() {
+ margin-left: @gridGutterWidth * -1;
+ .clearfix();
+}
+.makeColumn(@columns: 1, @offset: 0) {
+ float: left;
+ margin-left: (@gridColumnWidth * @offset) + (@gridGutterWidth * (@offset - 1)) + (@gridGutterWidth * 2);
+ width: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns - 1));
+}
+
+// The Grid
+#grid {
+
+ .core (@gridColumnWidth, @gridGutterWidth) {
+
+ .spanX (@index) when (@index > 0) {
+ (~".span@{index}") { .span(@index); }
+ .spanX(@index - 1);
+ }
+ .spanX (0) {}
+
+ .offsetX (@index) when (@index > 0) {
+ (~".offset@{index}") { .offset(@index); }
+ .offsetX(@index - 1);
+ }
+ .offsetX (0) {}
+
+ .offset (@columns) {
+ margin-left: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns + 1));
+ }
+
+ .span (@columns) {
+ width: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns - 1));
+ }
+
+ .row {
+ margin-left: @gridGutterWidth * -1;
+ .clearfix();
+ }
+
+ [class*="span"] {
+ float: left;
+ min-height: 1px; // prevent collapsing columns
+ margin-left: @gridGutterWidth;
+ }
+
+ // Set the container width, and override it for fixed navbars in media queries
+ .container,
+ .navbar-static-top .container,
+ .navbar-fixed-top .container,
+ .navbar-fixed-bottom .container { .span(@gridColumns); }
+
+ // generate .spanX and .offsetX
+ .spanX (@gridColumns);
+ .offsetX (@gridColumns);
+
+ }
+
+ .fluid (@fluidGridColumnWidth, @fluidGridGutterWidth) {
+
+ .spanX (@index) when (@index > 0) {
+ (~".span@{index}") { .span(@index); }
+ .spanX(@index - 1);
+ }
+ .spanX (0) {}
+
+ .offsetX (@index) when (@index > 0) {
+ (~'.offset@{index}') { .offset(@index); }
+ (~'.offset@{index}:first-child') { .offsetFirstChild(@index); }
+ .offsetX(@index - 1);
+ }
+ .offsetX (0) {}
+
+ .offset (@columns) {
+ margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) + (@fluidGridGutterWidth*2);
+ *margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%) + (@fluidGridGutterWidth*2) - (.5 / @gridRowWidth * 100 * 1%);
+ }
+
+ .offsetFirstChild (@columns) {
+ margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) + (@fluidGridGutterWidth);
+ *margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%) + @fluidGridGutterWidth - (.5 / @gridRowWidth * 100 * 1%);
+ }
+
+ .span (@columns) {
+ width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1));
+ *width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%);
+ }
+
+ .row-fluid {
+ width: 100%;
+ .clearfix();
+ [class*="span"] {
+ .input-block-level();
+ float: left;
+ margin-left: @fluidGridGutterWidth;
+ *margin-left: @fluidGridGutterWidth - (.5 / @gridRowWidth * 100 * 1%);
+ }
+ [class*="span"]:first-child {
+ margin-left: 0;
+ }
+
+ // generate .spanX and .offsetX
+ .spanX (@gridColumns);
+ .offsetX (@gridColumns);
+ }
+
+ }
+
+ .input(@gridColumnWidth, @gridGutterWidth) {
+
+ .spanX (@index) when (@index > 0) {
+ (~"input.span@{index}, textarea.span@{index}, .uneditable-input.span@{index}") { .span(@index); }
+ .spanX(@index - 1);
+ }
+ .spanX (0) {}
+
+ .span(@columns) {
+ width: ((@gridColumnWidth) * @columns) + (@gridGutterWidth * (@columns - 1)) - 14;
+ }
+
+ input,
+ textarea,
+ .uneditable-input {
+ margin-left: 0; // override margin-left from core grid system
+ }
+
+ // Space grid-sized controls properly if multiple per line
+ .controls-row [class*="span"] + [class*="span"] {
+ margin-left: @gridGutterWidth;
+ }
+
+ // generate .spanX
+ .spanX (@gridColumns);
+
+ }
+
+}
diff --git a/bootstrap/less/modals.less b/bootstrap/less/modals.less
new file mode 100644
index 0000000..81cacb7
--- /dev/null
+++ b/bootstrap/less/modals.less
@@ -0,0 +1,98 @@
+//
+// Modals
+// --------------------------------------------------
+
+
+// Recalculate z-index where appropriate,
+// but only apply to elements within modal
+.modal-open .modal {
+ .dropdown-menu { z-index: @zindexDropdown + @zindexModal; }
+ .dropdown.open { *z-index: @zindexDropdown + @zindexModal; }
+ .popover { z-index: @zindexPopover + @zindexModal; }
+ .tooltip { z-index: @zindexTooltip + @zindexModal; }
+}
+
+// Background
+.modal-backdrop {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: @zindexModalBackdrop;
+ background-color: @black;
+ // Fade for backdrop
+ &.fade { opacity: 0; }
+}
+
+.modal-backdrop,
+.modal-backdrop.fade.in {
+ .opacity(80);
+}
+
+// Base modal
+.modal {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ z-index: @zindexModal;
+ overflow: auto;
+ width: 560px;
+ margin: -250px 0 0 -280px;
+ background-color: @white;
+ border: 1px solid #999;
+ border: 1px solid rgba(0,0,0,.3);
+ *border: 1px solid #999; /* IE6-7 */
+ .border-radius(6px);
+ .box-shadow(0 3px 7px rgba(0,0,0,0.3));
+ .background-clip(padding-box);
+ &.fade {
+ .transition(e('opacity .3s linear, top .3s ease-out'));
+ top: -25%;
+ }
+ &.fade.in { top: 50%; }
+}
+.modal-header {
+ padding: 9px 15px;
+ border-bottom: 1px solid #eee;
+ // Close icon
+ .close { margin-top: 2px; }
+ // Heading
+ h3 {
+ margin: 0;
+ line-height: 30px;
+ }
+}
+
+// Body (where all modal content resides)
+.modal-body {
+ overflow-y: auto;
+ max-height: 400px;
+ padding: 15px;
+}
+// Remove bottom margin if need be
+.modal-form {
+ margin-bottom: 0;
+}
+
+// Footer (for actions)
+.modal-footer {
+ padding: 14px 15px 15px;
+ margin-bottom: 0;
+ text-align: right; // right align buttons
+ background-color: #f5f5f5;
+ border-top: 1px solid #ddd;
+ .border-radius(0 0 6px 6px);
+ .box-shadow(inset 0 1px 0 @white);
+ .clearfix(); // clear it in case folks use .pull-* classes on buttons
+
+ // Properly space out buttons
+ .btn + .btn {
+ margin-left: 5px;
+ margin-bottom: 0; // account for input[type="submit"] which gets the bottom margin like all other inputs
+ }
+ // but override that for button groups
+ .btn-group .btn + .btn {
+ margin-left: -1px;
+ }
+}
diff --git a/bootstrap/less/navbar.less b/bootstrap/less/navbar.less
new file mode 100644
index 0000000..3514c40
--- /dev/null
+++ b/bootstrap/less/navbar.less
@@ -0,0 +1,475 @@
+//
+// Navbars (Redux)
+// --------------------------------------------------
+
+
+// COMMON STYLES
+// -------------
+
+// Base class and wrapper
+.navbar {
+ overflow: visible;
+ margin-bottom: @baseLineHeight;
+ color: @navbarText;
+
+ // Fix for IE7's bad z-indexing so dropdowns don't appear below content that follows the navbar
+ *position: relative;
+ *z-index: 2;
+}
+
+// Inner for background effects
+// Gradient is applied to its own element because overflow visible is not honored by IE when filter is present
+.navbar-inner {
+ min-height: @navbarHeight;
+ padding-left: 20px;
+ padding-right: 20px;
+ #gradient > .vertical(@navbarBackgroundHighlight, @navbarBackground);
+ border: 1px solid @navbarBorder;
+ .border-radius(4px);
+ .box-shadow(0 1px 4px rgba(0,0,0,.065));
+
+ // Prevent floats from breaking the navbar
+ .clearfix();
+}
+
+// Set width to auto for default container
+// We then reset it for fixed navbars in the #gridSystem mixin
+.navbar .container {
+ width: auto;
+}
+
+// Override the default collapsed state
+.nav-collapse.collapse {
+ height: auto;
+}
+
+
+// Brand: website or project name
+// -------------------------
+.navbar .brand {
+ float: left;
+ display: block;
+ // Vertically center the text given @navbarHeight
+ padding: ((@navbarHeight - @baseLineHeight) / 2) 20px ((@navbarHeight - @baseLineHeight) / 2);
+ margin-left: -20px; // negative indent to left-align the text down the page
+ font-size: 20px;
+ font-weight: 200;
+ color: @navbarBrandColor;
+ text-shadow: 0 1px 0 @navbarBackgroundHighlight;
+ &:hover {
+ text-decoration: none;
+ }
+}
+
+// Plain text in topbar
+// -------------------------
+.navbar-text {
+ margin-bottom: 0;
+ line-height: @navbarHeight;
+}
+
+// Janky solution for now to account for links outside the .nav
+// -------------------------
+.navbar-link {
+ color: @navbarLinkColor;
+ &:hover {
+ color: @navbarLinkColorHover;
+ }
+}
+
+// Dividers in navbar
+// -------------------------
+.navbar .divider-vertical {
+ height: @navbarHeight;
+ margin: 0 9px;
+ border-left: 1px solid @navbarBackground;
+ border-right: 1px solid @navbarBackgroundHighlight;
+}
+
+// Buttons in navbar
+// -------------------------
+.navbar .btn,
+.navbar .btn-group {
+ .navbarVerticalAlign(30px); // Vertically center in navbar
+}
+.navbar .btn-group .btn,
+.navbar .input-prepend .btn,
+.navbar .input-append .btn {
+ margin-top: 0; // then undo the margin here so we don't accidentally double it
+}
+
+// Navbar forms
+// -------------------------
+.navbar-form {
+ margin-bottom: 0; // remove default bottom margin
+ .clearfix();
+ input,
+ select,
+ .radio,
+ .checkbox {
+ .navbarVerticalAlign(30px); // Vertically center in navbar
+ }
+ input,
+ select,
+ .btn {
+ display: inline-block;
+ margin-bottom: 0;
+ }
+ input[type="image"],
+ input[type="checkbox"],
+ input[type="radio"] {
+ margin-top: 3px;
+ }
+ .input-append,
+ .input-prepend {
+ margin-top: 6px;
+ white-space: nowrap; // preven two items from separating within a .navbar-form that has .pull-left
+ input {
+ margin-top: 0; // remove the margin on top since it's on the parent
+ }
+ }
+}
+
+// Navbar search
+// -------------------------
+.navbar-search {
+ position: relative;
+ float: left;
+ .navbarVerticalAlign(30px); // Vertically center in navbar
+ margin-bottom: 0;
+ .search-query {
+ margin-bottom: 0;
+ padding: 4px 14px;
+ #font > .sans-serif(13px, normal, 1);
+ .border-radius(15px); // redeclare because of specificity of the type attribute
+ }
+}
+
+
+
+// Static navbar
+// -------------------------
+
+.navbar-static-top {
+ position: static;
+ width: 100%;
+ margin-bottom: 0; // remove 18px margin for default navbar
+ .navbar-inner {
+ .border-radius(0);
+ }
+}
+
+
+
+// Fixed navbar
+// -------------------------
+
+// Shared (top/bottom) styles
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+ position: fixed;
+ right: 0;
+ left: 0;
+ z-index: @zindexFixedNavbar;
+ margin-bottom: 0; // remove 18px margin for default navbar
+}
+.navbar-fixed-top .navbar-inner,
+.navbar-static-top .navbar-inner {
+ border-width: 0 0 1px;
+}
+.navbar-fixed-bottom .navbar-inner {
+ border-width: 1px 0 0;
+}
+.navbar-fixed-top .navbar-inner,
+.navbar-fixed-bottom .navbar-inner {
+ padding-left: 0;
+ padding-right: 0;
+ .border-radius(0);
+}
+
+// Reset container width
+// Required here as we reset the width earlier on and the grid mixins don't override early enough
+.navbar-static-top .container,
+.navbar-fixed-top .container,
+.navbar-fixed-bottom .container {
+ #grid > .core > .span(@gridColumns);
+}
+
+// Fixed to top
+.navbar-fixed-top {
+ top: 0;
+}
+.navbar-fixed-top,
+.navbar-static-top {
+ .navbar-inner {
+ .box-shadow(inset 0 -1px 0 rgba(0,0,0,.1), 0 1px 10px rgba(0,0,0,.1));
+ }
+}
+
+// Fixed to bottom
+.navbar-fixed-bottom {
+ bottom: 0;
+ .navbar-inner {
+ .box-shadow(inset 0 1px 0 rgba(0,0,0,.1), 0 -1px 10px rgba(0,0,0,.1));
+ }
+}
+
+
+
+// NAVIGATION
+// ----------
+
+.navbar .nav {
+ position: relative;
+ left: 0;
+ display: block;
+ float: left;
+ margin: 0 10px 0 0;
+}
+.navbar .nav.pull-right {
+ float: right; // redeclare due to specificity
+ margin-right: 0; // remove margin on float right nav
+}
+.navbar .nav > li {
+ float: left;
+}
+
+// Links
+.navbar .nav > li > a {
+ float: none;
+ // Vertically center the text given @navbarHeight
+ padding: ((@navbarHeight - @baseLineHeight) / 2) 15px ((@navbarHeight - @baseLineHeight) / 2);
+ color: @navbarLinkColor;
+ text-decoration: none;
+ text-shadow: 0 1px 0 @navbarBackgroundHighlight;
+}
+.navbar .nav .dropdown-toggle .caret {
+ margin-top: 8px;
+}
+
+// Hover
+.navbar .nav > li > a:focus,
+.navbar .nav > li > a:hover {
+ background-color: @navbarLinkBackgroundHover; // "transparent" is default to differentiate :hover from .active
+ color: @navbarLinkColorHover;
+ text-decoration: none;
+}
+
+// Active nav items
+.navbar .nav > .active > a,
+.navbar .nav > .active > a:hover,
+.navbar .nav > .active > a:focus {
+ color: @navbarLinkColorActive;
+ text-decoration: none;
+ background-color: @navbarLinkBackgroundActive;
+ .box-shadow(inset 0 3px 8px rgba(0,0,0,.125));
+}
+
+// Navbar button for toggling navbar items in responsive layouts
+// These definitions need to come after '.navbar .btn'
+.navbar .btn-navbar {
+ display: none;
+ float: right;
+ padding: 7px 10px;
+ margin-left: 5px;
+ margin-right: 5px;
+ .buttonBackground(darken(@navbarBackgroundHighlight, 5%), darken(@navbarBackground, 5%));
+ .box-shadow(inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075));
+}
+.navbar .btn-navbar .icon-bar {
+ display: block;
+ width: 18px;
+ height: 2px;
+ background-color: #f5f5f5;
+ .border-radius(1px);
+ .box-shadow(0 1px 0 rgba(0,0,0,.25));
+}
+.btn-navbar .icon-bar + .icon-bar {
+ margin-top: 3px;
+}
+
+
+
+// Dropdown menus
+// --------------
+
+// Menu position and menu carets
+.navbar .nav > li > .dropdown-menu {
+ &:before {
+ content: '';
+ display: inline-block;
+ border-left: 7px solid transparent;
+ border-right: 7px solid transparent;
+ border-bottom: 7px solid #ccc;
+ border-bottom-color: @dropdownBorder;
+ position: absolute;
+ top: -7px;
+ left: 9px;
+ }
+ &:after {
+ content: '';
+ display: inline-block;
+ border-left: 6px solid transparent;
+ border-right: 6px solid transparent;
+ border-bottom: 6px solid @dropdownBackground;
+ position: absolute;
+ top: -6px;
+ left: 10px;
+ }
+}
+// Menu position and menu caret support for dropups via extra dropup class
+.navbar-fixed-bottom .nav > li > .dropdown-menu {
+ &:before {
+ border-top: 7px solid #ccc;
+ border-top-color: @dropdownBorder;
+ border-bottom: 0;
+ bottom: -7px;
+ top: auto;
+ }
+ &:after {
+ border-top: 6px solid @dropdownBackground;
+ border-bottom: 0;
+ bottom: -6px;
+ top: auto;
+ }
+}
+
+// Remove background color from open dropdown
+.navbar .nav li.dropdown.open > .dropdown-toggle,
+.navbar .nav li.dropdown.active > .dropdown-toggle,
+.navbar .nav li.dropdown.open.active > .dropdown-toggle {
+ background-color: @navbarLinkBackgroundActive;
+ color: @navbarLinkColorActive;
+}
+.navbar .nav li.dropdown > .dropdown-toggle .caret {
+ border-top-color: @navbarLinkColor;
+ border-bottom-color: @navbarLinkColor;
+}
+.navbar .nav li.dropdown.open > .dropdown-toggle .caret,
+.navbar .nav li.dropdown.active > .dropdown-toggle .caret,
+.navbar .nav li.dropdown.open.active > .dropdown-toggle .caret {
+ border-top-color: @navbarLinkColorActive;
+ border-bottom-color: @navbarLinkColorActive;
+}
+
+// Right aligned menus need alt position
+.navbar .pull-right > li > .dropdown-menu,
+.navbar .nav > li > .dropdown-menu.pull-right {
+ left: auto;
+ right: 0;
+ &:before {
+ left: auto;
+ right: 12px;
+ }
+ &:after {
+ left: auto;
+ right: 13px;
+ }
+ .dropdown-menu {
+ left: auto;
+ right: 100%;
+ margin-left: 0;
+ margin-right: -1px;
+ .border-radius(6px 0 6px 6px);
+ }
+}
+
+
+// Inverted navbar
+// -------------------------
+
+.navbar-inverse {
+ color: @navbarInverseText;
+
+ .navbar-inner {
+ #gradient > .vertical(@navbarInverseBackgroundHighlight, @navbarInverseBackground);
+ border-color: @navbarInverseBorder;
+ }
+
+ .brand,
+ .nav > li > a {
+ color: @navbarInverseLinkColor;
+ text-shadow: 0 -1px 0 rgba(0,0,0,.25);
+ &:hover {
+ color: @navbarInverseLinkColorHover;
+ }
+ }
+
+ .nav > li > a:focus,
+ .nav > li > a:hover {
+ background-color: @navbarInverseLinkBackgroundHover;
+ color: @navbarInverseLinkColorHover;
+ }
+
+ .nav .active > a,
+ .nav .active > a:hover,
+ .nav .active > a:focus {
+ color: @navbarInverseLinkColorActive;
+ background-color: @navbarInverseLinkBackgroundActive;
+ }
+
+ // Inline text links
+ .navbar-link {
+ color: @navbarInverseLinkColor;
+ &:hover {
+ color: @navbarInverseLinkColorHover;
+ }
+ }
+
+ // Dividers in navbar
+ .divider-vertical {
+ border-left-color: @navbarInverseBackground;
+ border-right-color: @navbarInverseBackgroundHighlight;
+ }
+
+ // Dropdowns
+ .nav li.dropdown.open > .dropdown-toggle,
+ .nav li.dropdown.active > .dropdown-toggle,
+ .nav li.dropdown.open.active > .dropdown-toggle {
+ background-color: @navbarInverseLinkBackgroundActive;
+ color: @navbarInverseLinkColorActive;
+ }
+ .nav li.dropdown > .dropdown-toggle .caret {
+ border-top-color: @navbarInverseLinkColor;
+ border-bottom-color: @navbarInverseLinkColor;
+ }
+ .nav li.dropdown.open > .dropdown-toggle .caret,
+ .nav li.dropdown.active > .dropdown-toggle .caret,
+ .nav li.dropdown.open.active > .dropdown-toggle .caret {
+ border-top-color: @navbarInverseLinkColorActive;
+ border-bottom-color: @navbarInverseLinkColorActive;
+ }
+
+ // Navbar search
+ .navbar-search {
+ .search-query {
+ color: @white;
+ background-color: @navbarInverseSearchBackground;
+ border-color: @navbarInverseSearchBorder;
+ .box-shadow(inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15));
+ .transition(none);
+ .placeholder(@navbarInverseSearchPlaceholderColor);
+
+ // Focus states (we use .focused since IE7-8 and down doesn't support :focus)
+ &:focus,
+ &.focused {
+ padding: 5px 15px;
+ color: @grayDark;
+ text-shadow: 0 1px 0 @white;
+ background-color: @navbarInverseSearchBackgroundFocus;
+ border: 0;
+ .box-shadow(0 0 3px rgba(0,0,0,.15));
+ outline: 0;
+ }
+ }
+ }
+
+ // Navbar collapse button
+ .btn-navbar {
+ .buttonBackground(darken(@navbarInverseBackgroundHighlight, 5%), darken(@navbarInverseBackground, 5%));
+ }
+
+}
+
+
+
diff --git a/bootstrap/less/navs.less b/bootstrap/less/navs.less
new file mode 100644
index 0000000..4c38f7d
--- /dev/null
+++ b/bootstrap/less/navs.less
@@ -0,0 +1,384 @@
+//
+// Navs
+// --------------------------------------------------
+
+
+// BASE CLASS
+// ----------
+
+.nav {
+ margin-left: 0;
+ margin-bottom: @baseLineHeight;
+ list-style: none;
+}
+
+// Make links block level
+.nav > li > a {
+ display: block;
+}
+.nav > li > a:hover {
+ text-decoration: none;
+ background-color: @grayLighter;
+}
+
+// Redeclare pull classes because of specifity
+.nav > .pull-right {
+ float: right;
+}
+
+// Nav headers (for dropdowns and lists)
+.nav-header {
+ display: block;
+ padding: 3px 15px;
+ font-size: 11px;
+ font-weight: bold;
+ line-height: @baseLineHeight;
+ color: @grayLight;
+ text-shadow: 0 1px 0 rgba(255,255,255,.5);
+ text-transform: uppercase;
+}
+// Space them out when they follow another list item (link)
+.nav li + .nav-header {
+ margin-top: 9px;
+}
+
+
+
+// NAV LIST
+// --------
+
+.nav-list {
+ padding-left: 15px;
+ padding-right: 15px;
+ margin-bottom: 0;
+}
+.nav-list > li > a,
+.nav-list .nav-header {
+ margin-left: -15px;
+ margin-right: -15px;
+ text-shadow: 0 1px 0 rgba(255,255,255,.5);
+}
+.nav-list > li > a {
+ padding: 3px 15px;
+}
+.nav-list > .active > a,
+.nav-list > .active > a:hover {
+ color: @white;
+ text-shadow: 0 -1px 0 rgba(0,0,0,.2);
+ background-color: @linkColor;
+}
+.nav-list [class^="icon-"] {
+ margin-right: 2px;
+}
+// Dividers (basically an hr) within the dropdown
+.nav-list .divider {
+ .nav-divider();
+}
+
+
+
+// TABS AND PILLS
+// -------------
+
+// Common styles
+.nav-tabs,
+.nav-pills {
+ .clearfix();
+}
+.nav-tabs > li,
+.nav-pills > li {
+ float: left;
+}
+.nav-tabs > li > a,
+.nav-pills > li > a {
+ padding-right: 12px;
+ padding-left: 12px;
+ margin-right: 2px;
+ line-height: 14px; // keeps the overall height an even number
+}
+
+// TABS
+// ----
+
+// Give the tabs something to sit on
+.nav-tabs {
+ border-bottom: 1px solid #ddd;
+}
+// Make the list-items overlay the bottom border
+.nav-tabs > li {
+ margin-bottom: -1px;
+}
+// Actual tabs (as links)
+.nav-tabs > li > a {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ line-height: @baseLineHeight;
+ border: 1px solid transparent;
+ .border-radius(4px 4px 0 0);
+ &:hover {
+ border-color: @grayLighter @grayLighter #ddd;
+ }
+}
+// Active state, and it's :hover to override normal :hover
+.nav-tabs > .active > a,
+.nav-tabs > .active > a:hover {
+ color: @gray;
+ background-color: @white;
+ border: 1px solid #ddd;
+ border-bottom-color: transparent;
+ cursor: default;
+}
+
+
+// PILLS
+// -----
+
+// Links rendered as pills
+.nav-pills > li > a {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ margin-top: 2px;
+ margin-bottom: 2px;
+ .border-radius(5px);
+}
+
+// Active state
+.nav-pills > .active > a,
+.nav-pills > .active > a:hover {
+ color: @white;
+ background-color: @linkColor;
+}
+
+
+
+// STACKED NAV
+// -----------
+
+// Stacked tabs and pills
+.nav-stacked > li {
+ float: none;
+}
+.nav-stacked > li > a {
+ margin-right: 0; // no need for the gap between nav items
+}
+
+// Tabs
+.nav-tabs.nav-stacked {
+ border-bottom: 0;
+}
+.nav-tabs.nav-stacked > li > a {
+ border: 1px solid #ddd;
+ .border-radius(0);
+}
+.nav-tabs.nav-stacked > li:first-child > a {
+ .border-top-radius(4px);
+}
+.nav-tabs.nav-stacked > li:last-child > a {
+ .border-bottom-radius(4px);
+}
+.nav-tabs.nav-stacked > li > a:hover {
+ border-color: #ddd;
+ z-index: 2;
+}
+
+// Pills
+.nav-pills.nav-stacked > li > a {
+ margin-bottom: 3px;
+}
+.nav-pills.nav-stacked > li:last-child > a {
+ margin-bottom: 1px; // decrease margin to match sizing of stacked tabs
+}
+
+
+
+// DROPDOWNS
+// ---------
+
+.nav-tabs .dropdown-menu {
+ .border-radius(0 0 6px 6px); // remove the top rounded corners here since there is a hard edge above the menu
+}
+.nav-pills .dropdown-menu {
+ .border-radius(6px); // make rounded corners match the pills
+}
+
+// Default dropdown links
+// -------------------------
+// Make carets use linkColor to start
+.nav .dropdown-toggle .caret {
+ border-top-color: @linkColor;
+ border-bottom-color: @linkColor;
+ margin-top: 6px;
+}
+.nav .dropdown-toggle:hover .caret {
+ border-top-color: @linkColorHover;
+ border-bottom-color: @linkColorHover;
+}
+/* move down carets for tabs */
+.nav-tabs .dropdown-toggle .caret {
+ margin-top: 8px;
+}
+
+// Active dropdown links
+// -------------------------
+.nav .active .dropdown-toggle .caret {
+ border-top-color: #fff;
+ border-bottom-color: #fff;
+}
+.nav-tabs .active .dropdown-toggle .caret {
+ border-top-color: @gray;
+ border-bottom-color: @gray;
+}
+
+// Active:hover dropdown links
+// -------------------------
+.nav > .dropdown.active > a:hover {
+ cursor: pointer;
+}
+
+// Open dropdowns
+// -------------------------
+.nav-tabs .open .dropdown-toggle,
+.nav-pills .open .dropdown-toggle,
+.nav > li.dropdown.open.active > a:hover {
+ color: @white;
+ background-color: @grayLight;
+ border-color: @grayLight;
+}
+.nav li.dropdown.open .caret,
+.nav li.dropdown.open.active .caret,
+.nav li.dropdown.open a:hover .caret {
+ border-top-color: @white;
+ border-bottom-color: @white;
+ .opacity(100);
+}
+
+// Dropdowns in stacked tabs
+.tabs-stacked .open > a:hover {
+ border-color: @grayLight;
+}
+
+
+
+// TABBABLE
+// --------
+
+
+// COMMON STYLES
+// -------------
+
+// Clear any floats
+.tabbable {
+ .clearfix();
+}
+.tab-content {
+ overflow: auto; // prevent content from running below tabs
+}
+
+// Remove border on bottom, left, right
+.tabs-below > .nav-tabs,
+.tabs-right > .nav-tabs,
+.tabs-left > .nav-tabs {
+ border-bottom: 0;
+}
+
+// Show/hide tabbable areas
+.tab-content > .tab-pane,
+.pill-content > .pill-pane {
+ display: none;
+}
+.tab-content > .active,
+.pill-content > .active {
+ display: block;
+}
+
+
+// BOTTOM
+// ------
+
+.tabs-below > .nav-tabs {
+ border-top: 1px solid #ddd;
+}
+.tabs-below > .nav-tabs > li {
+ margin-top: -1px;
+ margin-bottom: 0;
+}
+.tabs-below > .nav-tabs > li > a {
+ .border-radius(0 0 4px 4px);
+ &:hover {
+ border-bottom-color: transparent;
+ border-top-color: #ddd;
+ }
+}
+.tabs-below > .nav-tabs > .active > a,
+.tabs-below > .nav-tabs > .active > a:hover {
+ border-color: transparent #ddd #ddd #ddd;
+}
+
+// LEFT & RIGHT
+// ------------
+
+// Common styles
+.tabs-left > .nav-tabs > li,
+.tabs-right > .nav-tabs > li {
+ float: none;
+}
+.tabs-left > .nav-tabs > li > a,
+.tabs-right > .nav-tabs > li > a {
+ min-width: 74px;
+ margin-right: 0;
+ margin-bottom: 3px;
+}
+
+// Tabs on the left
+.tabs-left > .nav-tabs {
+ float: left;
+ margin-right: 19px;
+ border-right: 1px solid #ddd;
+}
+.tabs-left > .nav-tabs > li > a {
+ margin-right: -1px;
+ .border-radius(4px 0 0 4px);
+}
+.tabs-left > .nav-tabs > li > a:hover {
+ border-color: @grayLighter #ddd @grayLighter @grayLighter;
+}
+.tabs-left > .nav-tabs .active > a,
+.tabs-left > .nav-tabs .active > a:hover {
+ border-color: #ddd transparent #ddd #ddd;
+ *border-right-color: @white;
+}
+
+// Tabs on the right
+.tabs-right > .nav-tabs {
+ float: right;
+ margin-left: 19px;
+ border-left: 1px solid #ddd;
+}
+.tabs-right > .nav-tabs > li > a {
+ margin-left: -1px;
+ .border-radius(0 4px 4px 0);
+}
+.tabs-right > .nav-tabs > li > a:hover {
+ border-color: @grayLighter @grayLighter @grayLighter #ddd;
+}
+.tabs-right > .nav-tabs .active > a,
+.tabs-right > .nav-tabs .active > a:hover {
+ border-color: #ddd #ddd #ddd transparent;
+ *border-left-color: @white;
+}
+
+
+
+// DISABLED STATES
+// ---------------
+
+// Gray out text
+.nav > .disabled > a {
+ color: @grayLight;
+}
+// Nuke hover effects
+.nav > .disabled > a:hover {
+ text-decoration: none;
+ background-color: transparent;
+ cursor: default;
+}
diff --git a/bootstrap/less/pager.less b/bootstrap/less/pager.less
new file mode 100644
index 0000000..a7629d3
--- /dev/null
+++ b/bootstrap/less/pager.less
@@ -0,0 +1,40 @@
+//
+// Pager pagination
+// --------------------------------------------------
+
+
+.pager {
+ margin: @baseLineHeight 0;
+ list-style: none;
+ text-align: center;
+ .clearfix();
+}
+.pager li {
+ display: inline;
+}
+.pager a,
+.pager span {
+ display: inline-block;
+ padding: 5px 14px;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ .border-radius(15px);
+}
+.pager a:hover {
+ text-decoration: none;
+ background-color: #f5f5f5;
+}
+.pager .next a,
+.pager .next span {
+ float: right;
+}
+.pager .previous a {
+ float: left;
+}
+.pager .disabled a,
+.pager .disabled a:hover,
+.pager .disabled span {
+ color: @grayLight;
+ background-color: #fff;
+ cursor: default;
+}
\ No newline at end of file
diff --git a/bootstrap/less/pagination.less b/bootstrap/less/pagination.less
new file mode 100644
index 0000000..61a760b
--- /dev/null
+++ b/bootstrap/less/pagination.less
@@ -0,0 +1,64 @@
+//
+// Pagination (multiple pages)
+// --------------------------------------------------
+
+
+.pagination {
+ height: @baseLineHeight * 2;
+ margin: @baseLineHeight 0;
+ }
+.pagination ul {
+ display: inline-block;
+ .ie7-inline-block();
+ margin-left: 0;
+ margin-bottom: 0;
+ .border-radius(3px);
+ .box-shadow(0 1px 2px rgba(0,0,0,.05));
+}
+.pagination ul > li {
+ display: inline;
+}
+.pagination ul > li > a,
+.pagination ul > li > span {
+ float: left;
+ padding: 0 14px;
+ line-height: (@baseLineHeight * 2) - 2;
+ text-decoration: none;
+ background-color: @paginationBackground;
+ border: 1px solid @paginationBorder;
+ border-left-width: 0;
+}
+.pagination ul > li > a:hover,
+.pagination ul > .active > a,
+.pagination ul > .active > span {
+ background-color: #f5f5f5;
+}
+.pagination ul > .active > a,
+.pagination ul > .active > span {
+ color: @grayLight;
+ cursor: default;
+}
+.pagination ul > .disabled > span,
+.pagination ul > .disabled > a,
+.pagination ul > .disabled > a:hover {
+ color: @grayLight;
+ background-color: transparent;
+ cursor: default;
+}
+.pagination ul > li:first-child > a,
+.pagination ul > li:first-child > span {
+ border-left-width: 1px;
+ .border-radius(3px 0 0 3px);
+}
+.pagination ul > li:last-child > a,
+.pagination ul > li:last-child > span {
+ .border-radius(0 3px 3px 0);
+}
+
+// Centered
+.pagination-centered {
+ text-align: center;
+}
+.pagination-right {
+ text-align: right;
+}
diff --git a/bootstrap/less/popovers.less b/bootstrap/less/popovers.less
new file mode 100644
index 0000000..2b3f1b0
--- /dev/null
+++ b/bootstrap/less/popovers.less
@@ -0,0 +1,117 @@
+//
+// Popovers
+// --------------------------------------------------
+
+
+.popover {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: @zindexPopover;
+ display: none;
+ width: 236px;
+ padding: 1px;
+ background-color: @popoverBackground;
+ -webkit-background-clip: padding-box;
+ -moz-background-clip: padding;
+ background-clip: padding-box;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0,0,0,.2);
+ .border-radius(6px);
+ .box-shadow(0 5px 10px rgba(0,0,0,.2));
+
+ // Offset the popover to account for the popover arrow
+ &.top { margin-bottom: 10px; }
+ &.right { margin-left: 10px; }
+ &.bottom { margin-top: 10px; }
+ &.left { margin-right: 10px; }
+
+}
+
+.popover-title {
+ margin: 0; // reset heading margin
+ padding: 8px 14px;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 18px;
+ background-color: @popoverTitleBackground;
+ border-bottom: 1px solid darken(@popoverTitleBackground, 5%);
+ .border-radius(5px 5px 0 0);
+}
+
+.popover-content {
+ padding: 9px 14px;
+ p, ul, ol {
+ margin-bottom: 0;
+ }
+}
+
+// Arrows
+.popover .arrow,
+.popover .arrow:after {
+ position: absolute;
+ display: inline-block;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+}
+.popover .arrow:after {
+ content: "";
+ z-index: -1;
+}
+
+.popover {
+ &.top .arrow {
+ bottom: -@popoverArrowWidth;
+ left: 50%;
+ margin-left: -@popoverArrowWidth;
+ border-width: @popoverArrowWidth @popoverArrowWidth 0;
+ border-top-color: @popoverArrowColor;
+ &:after {
+ border-width: @popoverArrowOuterWidth @popoverArrowOuterWidth 0;
+ border-top-color: @popoverArrowOuterColor;
+ bottom: -1px;
+ left: -@popoverArrowOuterWidth;
+ }
+ }
+ &.right .arrow {
+ top: 50%;
+ left: -@popoverArrowWidth;
+ margin-top: -@popoverArrowWidth;
+ border-width: @popoverArrowWidth @popoverArrowWidth @popoverArrowWidth 0;
+ border-right-color: @popoverArrowColor;
+ &:after {
+ border-width: @popoverArrowOuterWidth @popoverArrowOuterWidth @popoverArrowOuterWidth 0;
+ border-right-color: @popoverArrowOuterColor;
+ bottom: -@popoverArrowOuterWidth;
+ left: -1px;
+ }
+ }
+ &.bottom .arrow {
+ top: -@popoverArrowWidth;
+ left: 50%;
+ margin-left: -@popoverArrowWidth;
+ border-width: 0 @popoverArrowWidth @popoverArrowWidth;
+ border-bottom-color: @popoverArrowColor;
+ &:after {
+ border-width: 0 @popoverArrowOuterWidth @popoverArrowOuterWidth;
+ border-bottom-color: @popoverArrowOuterColor;
+ top: -1px;
+ left: -@popoverArrowOuterWidth;
+ }
+ }
+ &.left .arrow {
+ top: 50%;
+ right: -@popoverArrowWidth;
+ margin-top: -@popoverArrowWidth;
+ border-width: @popoverArrowWidth 0 @popoverArrowWidth @popoverArrowWidth;
+ border-left-color: @popoverArrowColor;
+ &:after {
+ border-width: @popoverArrowOuterWidth 0 @popoverArrowOuterWidth @popoverArrowOuterWidth;
+ border-left-color: @popoverArrowOuterColor;
+ bottom: -@popoverArrowOuterWidth;
+ right: -1px;
+ }
+ }
+}
diff --git a/bootstrap/less/progress-bars.less b/bootstrap/less/progress-bars.less
new file mode 100644
index 0000000..0486371
--- /dev/null
+++ b/bootstrap/less/progress-bars.less
@@ -0,0 +1,122 @@
+//
+// Progress bars
+// --------------------------------------------------
+
+
+// ANIMATIONS
+// ----------
+
+// Webkit
+@-webkit-keyframes progress-bar-stripes {
+ from { background-position: 40px 0; }
+ to { background-position: 0 0; }
+}
+
+// Firefox
+@-moz-keyframes progress-bar-stripes {
+ from { background-position: 40px 0; }
+ to { background-position: 0 0; }
+}
+
+// IE9
+@-ms-keyframes progress-bar-stripes {
+ from { background-position: 40px 0; }
+ to { background-position: 0 0; }
+}
+
+// Opera
+@-o-keyframes progress-bar-stripes {
+ from { background-position: 0 0; }
+ to { background-position: 40px 0; }
+}
+
+// Spec
+@keyframes progress-bar-stripes {
+ from { background-position: 40px 0; }
+ to { background-position: 0 0; }
+}
+
+
+
+// THE BARS
+// --------
+
+// Outer container
+.progress {
+ overflow: hidden;
+ height: @baseLineHeight;
+ margin-bottom: @baseLineHeight;
+ #gradient > .vertical(#f5f5f5, #f9f9f9);
+ .box-shadow(inset 0 1px 2px rgba(0,0,0,.1));
+ .border-radius(4px);
+}
+
+// Bar of progress
+.progress .bar {
+ width: 0%;
+ height: 100%;
+ color: @white;
+ float: left;
+ font-size: 12px;
+ text-align: center;
+ text-shadow: 0 -1px 0 rgba(0,0,0,.25);
+ #gradient > .vertical(#149bdf, #0480be);
+ .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15));
+ .box-sizing(border-box);
+ .transition(width .6s ease);
+}
+.progress .bar + .bar {
+ .box-shadow(inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15));
+}
+
+// Striped bars
+.progress-striped .bar {
+ #gradient > .striped(#149bdf);
+ .background-size(40px 40px);
+}
+
+// Call animation for the active one
+.progress.active .bar {
+ -webkit-animation: progress-bar-stripes 2s linear infinite;
+ -moz-animation: progress-bar-stripes 2s linear infinite;
+ -ms-animation: progress-bar-stripes 2s linear infinite;
+ -o-animation: progress-bar-stripes 2s linear infinite;
+ animation: progress-bar-stripes 2s linear infinite;
+}
+
+
+
+// COLORS
+// ------
+
+// Danger (red)
+.progress-danger .bar, .progress .bar-danger {
+ #gradient > .vertical(#ee5f5b, #c43c35);
+}
+.progress-danger.progress-striped .bar, .progress-striped .bar-danger {
+ #gradient > .striped(#ee5f5b);
+}
+
+// Success (green)
+.progress-success .bar, .progress .bar-success {
+ #gradient > .vertical(#62c462, #57a957);
+}
+.progress-success.progress-striped .bar, .progress-striped .bar-success {
+ #gradient > .striped(#62c462);
+}
+
+// Info (teal)
+.progress-info .bar, .progress .bar-info {
+ #gradient > .vertical(#5bc0de, #339bb9);
+}
+.progress-info.progress-striped .bar, .progress-striped .bar-info {
+ #gradient > .striped(#5bc0de);
+}
+
+// Warning (orange)
+.progress-warning .bar, .progress .bar-warning {
+ #gradient > .vertical(lighten(@orange, 15%), @orange);
+}
+.progress-warning.progress-striped .bar, .progress-striped .bar-warning {
+ #gradient > .striped(lighten(@orange, 15%));
+}
diff --git a/bootstrap/less/reset.less b/bootstrap/less/reset.less
new file mode 100644
index 0000000..2901a85
--- /dev/null
+++ b/bootstrap/less/reset.less
@@ -0,0 +1,137 @@
+//
+// Modals
+// Adapted from http://github.com/necolas/normalize.css
+// --------------------------------------------------
+
+
+// Display in IE6-9 and FF3
+// -------------------------
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+nav,
+section {
+ display: block;
+}
+
+// Display block in IE6-9 and FF3
+// -------------------------
+
+audio,
+canvas,
+video {
+ display: inline-block;
+ *display: inline;
+ *zoom: 1;
+}
+
+// Prevents modern browsers from displaying 'audio' without controls
+// -------------------------
+
+audio:not([controls]) {
+ display: none;
+}
+
+// Base settings
+// -------------------------
+
+html {
+ font-size: 100%;
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+}
+// Focus states
+a:focus {
+ .tab-focus();
+}
+// Hover & Active
+a:hover,
+a:active {
+ outline: 0;
+}
+
+// Prevents sub and sup affecting line-height in all browsers
+// -------------------------
+
+sub,
+sup {
+ position: relative;
+ font-size: 75%;
+ line-height: 0;
+ vertical-align: baseline;
+}
+sup {
+ top: -0.5em;
+}
+sub {
+ bottom: -0.25em;
+}
+
+// Img border in a's and image quality
+// -------------------------
+
+img {
+ /* Responsive images (ensure images don't scale beyond their parents) */
+ max-width: 100%; /* Part 1: Set a maxium relative to the parent */
+ width: auto\9; /* IE7-8 need help adjusting responsive images */
+ height: auto; /* Part 2: Scale the height according to the width, otherwise you get stretching */
+
+ vertical-align: middle;
+ border: 0;
+ -ms-interpolation-mode: bicubic;
+}
+
+// Prevent max-width from affecting Google Maps
+#map_canvas img {
+ max-width: none;
+}
+
+// Forms
+// -------------------------
+
+// Font size in all browsers, margin changes, misc consistency
+button,
+input,
+select,
+textarea {
+ margin: 0;
+ font-size: 100%;
+ vertical-align: middle;
+}
+button,
+input {
+ *overflow: visible; // Inner spacing ie IE6/7
+ line-height: normal; // FF3/4 have !important on line-height in UA stylesheet
+}
+button::-moz-focus-inner,
+input::-moz-focus-inner { // Inner padding and border oddities in FF3/4
+ padding: 0;
+ border: 0;
+}
+button,
+input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+ cursor: pointer; // Cursors on all buttons applied consistently
+ -webkit-appearance: button; // Style clickable inputs in iOS
+}
+input[type="search"] { // Appearance in Safari/Chrome
+ -webkit-box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+ -webkit-appearance: textfield;
+}
+input[type="search"]::-webkit-search-decoration,
+input[type="search"]::-webkit-search-cancel-button {
+ -webkit-appearance: none; // Inner-padding issues in Chrome OSX, Safari 5
+}
+textarea {
+ overflow: auto; // Remove vertical scrollbar in IE6-9
+ vertical-align: top; // Readability and alignment cross-browser
+}
diff --git a/bootstrap/less/responsive-1200px-min.less b/bootstrap/less/responsive-1200px-min.less
new file mode 100644
index 0000000..4f35ba6
--- /dev/null
+++ b/bootstrap/less/responsive-1200px-min.less
@@ -0,0 +1,28 @@
+//
+// Responsive: Large desktop and up
+// --------------------------------------------------
+
+
+@media (min-width: 1200px) {
+
+ // Fixed grid
+ #grid > .core(@gridColumnWidth1200, @gridGutterWidth1200);
+
+ // Fluid grid
+ #grid > .fluid(@fluidGridColumnWidth1200, @fluidGridGutterWidth1200);
+
+ // Input grid
+ #grid > .input(@gridColumnWidth1200, @gridGutterWidth1200);
+
+ // Thumbnails
+ .thumbnails {
+ margin-left: -@gridGutterWidth1200;
+ }
+ .thumbnails > li {
+ margin-left: @gridGutterWidth1200;
+ }
+ .row-fluid .thumbnails {
+ margin-left: 0;
+ }
+
+}
diff --git a/bootstrap/less/responsive-767px-max.less b/bootstrap/less/responsive-767px-max.less
new file mode 100644
index 0000000..79c7eaa
--- /dev/null
+++ b/bootstrap/less/responsive-767px-max.less
@@ -0,0 +1,174 @@
+//
+// Responsive: Landscape phone to desktop/tablet
+// --------------------------------------------------
+
+
+@media (max-width: 767px) {
+
+ // Padding to set content in a bit
+ body {
+ padding-left: 20px;
+ padding-right: 20px;
+ }
+ // Negative indent the now static "fixed" navbar
+ .navbar-fixed-top,
+ .navbar-fixed-bottom,
+ .navbar-static-top {
+ margin-left: -20px;
+ margin-right: -20px;
+ }
+ // Remove padding on container given explicit padding set on body
+ .container-fluid {
+ padding: 0;
+ }
+
+ // TYPOGRAPHY
+ // ----------
+ // Reset horizontal dl
+ .dl-horizontal {
+ dt {
+ float: none;
+ clear: none;
+ width: auto;
+ text-align: left;
+ }
+ dd {
+ margin-left: 0;
+ }
+ }
+
+ // GRID & CONTAINERS
+ // -----------------
+ // Remove width from containers
+ .container {
+ width: auto;
+ }
+ // Fluid rows
+ .row-fluid {
+ width: 100%;
+ }
+ // Undo negative margin on rows and thumbnails
+ .row,
+ .thumbnails {
+ margin-left: 0;
+ }
+ .thumbnails > li {
+ float: none;
+ margin-left: 0; // Reset the default margin for all li elements when no .span* classes are present
+ }
+ // Make all grid-sized elements block level again
+ [class*="span"],
+ .row-fluid [class*="span"] {
+ float: none;
+ display: block;
+ width: 100%;
+ margin-left: 0;
+ .box-sizing(border-box);
+ }
+ .span12,
+ .row-fluid .span12 {
+ width: 100%;
+ .box-sizing(border-box);
+ }
+
+ // FORM FIELDS
+ // -----------
+ // Make span* classes full width
+ .input-large,
+ .input-xlarge,
+ .input-xxlarge,
+ input[class*="span"],
+ select[class*="span"],
+ textarea[class*="span"],
+ .uneditable-input {
+ .input-block-level();
+ }
+ // But don't let it screw up prepend/append inputs
+ .input-prepend input,
+ .input-append input,
+ .input-prepend input[class*="span"],
+ .input-append input[class*="span"] {
+ display: inline-block; // redeclare so they don't wrap to new lines
+ width: auto;
+ }
+ .controls-row [class*="span"] + [class*="span"] {
+ margin-left: 0;
+ }
+
+ // Modals
+ .modal {
+ position: fixed;
+ top: 20px;
+ left: 20px;
+ right: 20px;
+ width: auto;
+ margin: 0;
+ &.fade.in { top: auto; }
+ }
+
+}
+
+
+
+// UP TO LANDSCAPE PHONE
+// ---------------------
+
+@media (max-width: 480px) {
+
+ // Smooth out the collapsing/expanding nav
+ .nav-collapse {
+ -webkit-transform: translate3d(0, 0, 0); // activate the GPU
+ }
+
+ // Block level the page header small tag for readability
+ .page-header h1 small {
+ display: block;
+ line-height: @baseLineHeight;
+ }
+
+ // Update checkboxes for iOS
+ input[type="checkbox"],
+ input[type="radio"] {
+ border: 1px solid #ccc;
+ }
+
+ // Remove the horizontal form styles
+ .form-horizontal {
+ .control-label {
+ float: none;
+ width: auto;
+ padding-top: 0;
+ text-align: left;
+ }
+ // Move over all input controls and content
+ .controls {
+ margin-left: 0;
+ }
+ // Move the options list down to align with labels
+ .control-list {
+ padding-top: 0; // has to be padding because margin collaspes
+ }
+ // Move over buttons in .form-actions to align with .controls
+ .form-actions {
+ padding-left: 10px;
+ padding-right: 10px;
+ }
+ }
+
+ // Modals
+ .modal {
+ top: 10px;
+ left: 10px;
+ right: 10px;
+ }
+ .modal-header .close {
+ padding: 10px;
+ margin: -10px;
+ }
+
+ // Carousel
+ .carousel-caption {
+ position: static;
+ }
+
+}
diff --git a/bootstrap/less/responsive-768px-979px.less b/bootstrap/less/responsive-768px-979px.less
new file mode 100644
index 0000000..8e8c486
--- /dev/null
+++ b/bootstrap/less/responsive-768px-979px.less
@@ -0,0 +1,19 @@
+//
+// Responsive: Tablet to desktop
+// --------------------------------------------------
+
+
+@media (min-width: 768px) and (max-width: 979px) {
+
+ // Fixed grid
+ #grid > .core(@gridColumnWidth768, @gridGutterWidth768);
+
+ // Fluid grid
+ #grid > .fluid(@fluidGridColumnWidth768, @fluidGridGutterWidth768);
+
+ // Input grid
+ #grid > .input(@gridColumnWidth768, @gridGutterWidth768);
+
+ // No need to reset .thumbnails here since it's the same @gridGutterWidth
+
+}
diff --git a/bootstrap/less/responsive-navbar.less b/bootstrap/less/responsive-navbar.less
new file mode 100644
index 0000000..c454dd5
--- /dev/null
+++ b/bootstrap/less/responsive-navbar.less
@@ -0,0 +1,177 @@
+//
+// Responsive: Navbar
+// --------------------------------------------------
+
+
+// TABLETS AND BELOW
+// -----------------
+@media (max-width: @navbarCollapseWidth) {
+
+ // UNFIX THE TOPBAR
+ // ----------------
+ // Remove any padding from the body
+ body {
+ padding-top: 0;
+ }
+ // Unfix the navbars
+ .navbar-fixed-top,
+ .navbar-fixed-bottom {
+ position: static;
+ }
+ .navbar-fixed-top {
+ margin-bottom: @baseLineHeight;
+ }
+ .navbar-fixed-bottom {
+ margin-top: @baseLineHeight;
+ }
+ .navbar-fixed-top .navbar-inner,
+ .navbar-fixed-bottom .navbar-inner {
+ padding: 5px;
+ }
+ .navbar .container {
+ width: auto;
+ padding: 0;
+ }
+ // Account for brand name
+ .navbar .brand {
+ padding-left: 10px;
+ padding-right: 10px;
+ margin: 0 0 0 -5px;
+ }
+
+ // COLLAPSIBLE NAVBAR
+ // ------------------
+ // Nav collapse clears brand
+ .nav-collapse {
+ clear: both;
+ }
+ // Block-level the nav
+ .nav-collapse .nav {
+ float: none;
+ margin: 0 0 (@baseLineHeight / 2);
+ }
+ .nav-collapse .nav > li {
+ float: none;
+ }
+ .nav-collapse .nav > li > a {
+ margin-bottom: 2px;
+ }
+ .nav-collapse .nav > .divider-vertical {
+ display: none;
+ }
+ .nav-collapse .nav .nav-header {
+ color: @navbarText;
+ text-shadow: none;
+ }
+ // Nav and dropdown links in navbar
+ .nav-collapse .nav > li > a,
+ .nav-collapse .dropdown-menu a {
+ padding: 9px 15px;
+ font-weight: bold;
+ color: @navbarLinkColor;
+ .border-radius(3px);
+ }
+ // Buttons
+ .nav-collapse .btn {
+ padding: 4px 10px 4px;
+ font-weight: normal;
+ .border-radius(4px);
+ }
+ .nav-collapse .dropdown-menu li + li a {
+ margin-bottom: 2px;
+ }
+ .nav-collapse .nav > li > a:hover,
+ .nav-collapse .dropdown-menu a:hover {
+ background-color: @navbarBackground;
+ }
+ .navbar-inverse .nav-collapse .nav > li > a:hover,
+ .navbar-inverse .nav-collapse .dropdown-menu a:hover {
+ background-color: @navbarInverseBackground;
+ }
+ // Buttons in the navbar
+ .nav-collapse.in .btn-group {
+ margin-top: 5px;
+ padding: 0;
+ }
+ // Dropdowns in the navbar
+ .nav-collapse .dropdown-menu {
+ position: static;
+ top: auto;
+ left: auto;
+ float: none;
+ display: block;
+ max-width: none;
+ margin: 0 15px;
+ padding: 0;
+ background-color: transparent;
+ border: none;
+ .border-radius(0);
+ .box-shadow(none);
+ }
+ .nav-collapse .dropdown-menu:before,
+ .nav-collapse .dropdown-menu:after {
+ display: none;
+ }
+ .nav-collapse .dropdown-menu .divider {
+ display: none;
+ }
+ .nav-collapse .nav > li > .dropdown-menu {
+ &:before,
+ &:after {
+ display: none;
+ }
+ }
+ // Forms in navbar
+ .nav-collapse .navbar-form,
+ .nav-collapse .navbar-search {
+ float: none;
+ padding: (@baseLineHeight / 2) 15px;
+ margin: (@baseLineHeight / 2) 0;
+ border-top: 1px solid @navbarBackground;
+ border-bottom: 1px solid @navbarBackground;
+ .box-shadow(inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1));
+ }
+ .navbar-inverse .nav-collapse .navbar-form,
+ .navbar-inverse .nav-collapse .navbar-search {
+ border-top-color: @navbarInverseBackground;
+ border-bottom-color: @navbarInverseBackground;
+ }
+ // Pull right (secondary) nav content
+ .navbar .nav-collapse .nav.pull-right {
+ float: none;
+ margin-left: 0;
+ }
+ // Hide everything in the navbar save .brand and toggle button */
+ .nav-collapse,
+ .nav-collapse.collapse {
+ overflow: hidden;
+ height: 0;
+ }
+ // Navbar button
+ .navbar .btn-navbar {
+ display: block;
+ }
+
+ // STATIC NAVBAR
+ // -------------
+ .navbar-static .navbar-inner {
+ padding-left: 10px;
+ padding-right: 10px;
+ }
+
+
+}
+
+
+// DEFAULT DESKTOP
+// ---------------
+
+@media (min-width: 980px) {
+
+ // Required to make the collapsing navbar work on regular desktops
+ .nav-collapse.collapse {
+ height: auto !important;
+ overflow: visible !important;
+ }
+
+}
diff --git a/bootstrap/less/responsive-utilities.less b/bootstrap/less/responsive-utilities.less
new file mode 100644
index 0000000..2c3f6c1
--- /dev/null
+++ b/bootstrap/less/responsive-utilities.less
@@ -0,0 +1,43 @@
+//
+// Responsive: Utility classes
+// --------------------------------------------------
+
+
+// Hide from screenreaders and browsers
+// Credit: HTML5 Boilerplate
+.hidden {
+ display: none;
+ visibility: hidden;
+}
+
+// Visibility utilities
+
+// For desktops
+.visible-phone { display: none !important; }
+.visible-tablet { display: none !important; }
+.hidden-phone { }
+.hidden-tablet { }
+.hidden-desktop { display: none !important; }
+.visible-desktop { display: inherit !important; }
+
+// Tablets & small desktops only
+@media (min-width: 768px) and (max-width: 979px) {
+ // Hide everything else
+ .hidden-desktop { display: inherit !important; }
+ .visible-desktop { display: none !important ; }
+ // Show
+ .visible-tablet { display: inherit !important; }
+ // Hide
+ .hidden-tablet { display: none !important; }
+}
+
+// Phones only
+@media (max-width: 767px) {
+ // Hide everything else
+ .hidden-desktop { display: inherit !important; }
+ .visible-desktop { display: none !important; }
+ // Show
+ .visible-phone { display: inherit !important; } // Use inherit to restore previous behavior
+ // Hide
+ .hidden-phone { display: none !important; }
+}
diff --git a/bootstrap/less/responsive.less b/bootstrap/less/responsive.less
new file mode 100644
index 0000000..87b3470
--- /dev/null
+++ b/bootstrap/less/responsive.less
@@ -0,0 +1,48 @@
+/*!
+ * Bootstrap Responsive v2.1.1
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+
+
+// Responsive.less
+// For phone and tablet devices
+// -------------------------------------------------------------
+
+
+// REPEAT VARIABLES & MIXINS
+// -------------------------
+// Required since we compile the responsive stuff separately
+
+@import "variables.less"; // Modify this for custom colors, font-sizes, etc
+@import "mixins.less";
+
+
+// RESPONSIVE CLASSES
+// ------------------
+
+@import "responsive-utilities.less";
+
+
+// MEDIA QUERIES
+// ------------------
+
+// Large desktops
+@import "responsive-1200px-min.less";
+
+// Tablets to regular desktops
+@import "responsive-768px-979px.less";
+
+// Phones to portrait tablets and narrow desktops
+@import "responsive-767px-max.less";
+
+
+// RESPONSIVE NAVBAR
+// ------------------
+
+// From 979px and below, show a button to toggle navbar contents
+@import "responsive-navbar.less";
diff --git a/bootstrap/less/scaffolding.less b/bootstrap/less/scaffolding.less
new file mode 100644
index 0000000..7a7496a
--- /dev/null
+++ b/bootstrap/less/scaffolding.less
@@ -0,0 +1,52 @@
+//
+// Scaffolding
+// --------------------------------------------------
+
+
+// Body reset
+// -------------------------
+
+body {
+ margin: 0;
+ font-family: @baseFontFamily;
+ font-size: @baseFontSize;
+ line-height: @baseLineHeight;
+ color: @textColor;
+ background-color: @bodyBackground;
+}
+
+
+// Links
+// -------------------------
+
+a {
+ color: @linkColor;
+ text-decoration: none;
+}
+a:hover {
+ color: @linkColorHover;
+ text-decoration: underline;
+}
+
+
+// Images
+// -------------------------
+
+// Rounded corners
+.img-rounded {
+ .border-radius(6px);
+}
+
+// Add polaroid-esque trim
+.img-polaroid {
+ padding: 4px;
+ background-color: #fff;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0,0,0,.2);
+ .box-shadow(0 1px 3px rgba(0,0,0,.1));
+}
+
+// Perfect circle
+.img-circle {
+ .border-radius(500px); // crank the border-radius so it works with most reasonably sized images
+}
diff --git a/bootstrap/less/sprites.less b/bootstrap/less/sprites.less
new file mode 100644
index 0000000..9bf970f
--- /dev/null
+++ b/bootstrap/less/sprites.less
@@ -0,0 +1,193 @@
+//
+// Sprites
+// --------------------------------------------------
+
+
+// ICONS
+// -----
+
+// All icons receive the styles of the tag with a base class
+// of .i and are then given a unique class to add width, height,
+// and background-position. Your resulting HTML will look like
+// .
+
+// For the white version of the icons, just add the .icon-white class:
+//
+
+[class^="icon-"],
+[class*=" icon-"] {
+ display: inline-block;
+ width: 14px;
+ height: 14px;
+ .ie7-restore-right-whitespace();
+ line-height: 14px;
+ vertical-align: text-top;
+ background-image: url("@{iconSpritePath}");
+ background-position: 14px 14px;
+ background-repeat: no-repeat;
+ margin-top: 1px;
+}
+
+/* White icons with optional class, or on hover/active states of certain elements */
+.icon-white,
+.nav-tabs > .active > a > [class^="icon-"],
+.nav-tabs > .active > a > [class*=" icon-"],
+.nav-pills > .active > a > [class^="icon-"],
+.nav-pills > .active > a > [class*=" icon-"],
+.nav-list > .active > a > [class^="icon-"],
+.nav-list > .active > a > [class*=" icon-"],
+.navbar-inverse .nav > .active > a > [class^="icon-"],
+.navbar-inverse .nav > .active > a > [class*=" icon-"],
+.dropdown-menu > li > a:hover > [class^="icon-"],
+.dropdown-menu > li > a:hover > [class*=" icon-"],
+.dropdown-menu > .active > a > [class^="icon-"],
+.dropdown-menu > .active > a > [class*=" icon-"] {
+ background-image: url("@{iconWhiteSpritePath}");
+}
+
+.icon-glass { background-position: 0 0; }
+.icon-music { background-position: -24px 0; }
+.icon-search { background-position: -48px 0; }
+.icon-envelope { background-position: -72px 0; }
+.icon-heart { background-position: -96px 0; }
+.icon-star { background-position: -120px 0; }
+.icon-star-empty { background-position: -144px 0; }
+.icon-user { background-position: -168px 0; }
+.icon-film { background-position: -192px 0; }
+.icon-th-large { background-position: -216px 0; }
+.icon-th { background-position: -240px 0; }
+.icon-th-list { background-position: -264px 0; }
+.icon-ok { background-position: -288px 0; }
+.icon-remove { background-position: -312px 0; }
+.icon-zoom-in { background-position: -336px 0; }
+.icon-zoom-out { background-position: -360px 0; }
+.icon-off { background-position: -384px 0; }
+.icon-signal { background-position: -408px 0; }
+.icon-cog { background-position: -432px 0; }
+.icon-trash { background-position: -456px 0; }
+
+.icon-home { background-position: 0 -24px; }
+.icon-file { background-position: -24px -24px; }
+.icon-time { background-position: -48px -24px; }
+.icon-road { background-position: -72px -24px; }
+.icon-download-alt { background-position: -96px -24px; }
+.icon-download { background-position: -120px -24px; }
+.icon-upload { background-position: -144px -24px; }
+.icon-inbox { background-position: -168px -24px; }
+.icon-play-circle { background-position: -192px -24px; }
+.icon-repeat { background-position: -216px -24px; }
+.icon-refresh { background-position: -240px -24px; }
+.icon-list-alt { background-position: -264px -24px; }
+.icon-lock { background-position: -287px -24px; } // 1px off
+.icon-flag { background-position: -312px -24px; }
+.icon-headphones { background-position: -336px -24px; }
+.icon-volume-off { background-position: -360px -24px; }
+.icon-volume-down { background-position: -384px -24px; }
+.icon-volume-up { background-position: -408px -24px; }
+.icon-qrcode { background-position: -432px -24px; }
+.icon-barcode { background-position: -456px -24px; }
+
+.icon-tag { background-position: 0 -48px; }
+.icon-tags { background-position: -25px -48px; } // 1px off
+.icon-book { background-position: -48px -48px; }
+.icon-bookmark { background-position: -72px -48px; }
+.icon-print { background-position: -96px -48px; }
+.icon-camera { background-position: -120px -48px; }
+.icon-font { background-position: -144px -48px; }
+.icon-bold { background-position: -167px -48px; } // 1px off
+.icon-italic { background-position: -192px -48px; }
+.icon-text-height { background-position: -216px -48px; }
+.icon-text-width { background-position: -240px -48px; }
+.icon-align-left { background-position: -264px -48px; }
+.icon-align-center { background-position: -288px -48px; }
+.icon-align-right { background-position: -312px -48px; }
+.icon-align-justify { background-position: -336px -48px; }
+.icon-list { background-position: -360px -48px; }
+.icon-indent-left { background-position: -384px -48px; }
+.icon-indent-right { background-position: -408px -48px; }
+.icon-facetime-video { background-position: -432px -48px; }
+.icon-picture { background-position: -456px -48px; }
+
+.icon-pencil { background-position: 0 -72px; }
+.icon-map-marker { background-position: -24px -72px; }
+.icon-adjust { background-position: -48px -72px; }
+.icon-tint { background-position: -72px -72px; }
+.icon-edit { background-position: -96px -72px; }
+.icon-share { background-position: -120px -72px; }
+.icon-check { background-position: -144px -72px; }
+.icon-move { background-position: -168px -72px; }
+.icon-step-backward { background-position: -192px -72px; }
+.icon-fast-backward { background-position: -216px -72px; }
+.icon-backward { background-position: -240px -72px; }
+.icon-play { background-position: -264px -72px; }
+.icon-pause { background-position: -288px -72px; }
+.icon-stop { background-position: -312px -72px; }
+.icon-forward { background-position: -336px -72px; }
+.icon-fast-forward { background-position: -360px -72px; }
+.icon-step-forward { background-position: -384px -72px; }
+.icon-eject { background-position: -408px -72px; }
+.icon-chevron-left { background-position: -432px -72px; }
+.icon-chevron-right { background-position: -456px -72px; }
+
+.icon-plus-sign { background-position: 0 -96px; }
+.icon-minus-sign { background-position: -24px -96px; }
+.icon-remove-sign { background-position: -48px -96px; }
+.icon-ok-sign { background-position: -72px -96px; }
+.icon-question-sign { background-position: -96px -96px; }
+.icon-info-sign { background-position: -120px -96px; }
+.icon-screenshot { background-position: -144px -96px; }
+.icon-remove-circle { background-position: -168px -96px; }
+.icon-ok-circle { background-position: -192px -96px; }
+.icon-ban-circle { background-position: -216px -96px; }
+.icon-arrow-left { background-position: -240px -96px; }
+.icon-arrow-right { background-position: -264px -96px; }
+.icon-arrow-up { background-position: -289px -96px; } // 1px off
+.icon-arrow-down { background-position: -312px -96px; }
+.icon-share-alt { background-position: -336px -96px; }
+.icon-resize-full { background-position: -360px -96px; }
+.icon-resize-small { background-position: -384px -96px; }
+.icon-plus { background-position: -408px -96px; }
+.icon-minus { background-position: -433px -96px; }
+.icon-asterisk { background-position: -456px -96px; }
+
+.icon-exclamation-sign { background-position: 0 -120px; }
+.icon-gift { background-position: -24px -120px; }
+.icon-leaf { background-position: -48px -120px; }
+.icon-fire { background-position: -72px -120px; }
+.icon-eye-open { background-position: -96px -120px; }
+.icon-eye-close { background-position: -120px -120px; }
+.icon-warning-sign { background-position: -144px -120px; }
+.icon-plane { background-position: -168px -120px; }
+.icon-calendar { background-position: -192px -120px; }
+.icon-random { background-position: -216px -120px; width: 16px; }
+.icon-comment { background-position: -240px -120px; }
+.icon-magnet { background-position: -264px -120px; }
+.icon-chevron-up { background-position: -288px -120px; }
+.icon-chevron-down { background-position: -313px -119px; } // 1px, 1px off
+.icon-retweet { background-position: -336px -120px; }
+.icon-shopping-cart { background-position: -360px -120px; }
+.icon-folder-close { background-position: -384px -120px; }
+.icon-folder-open { background-position: -408px -120px; width: 16px; }
+.icon-resize-vertical { background-position: -432px -119px; } // 1px, 1px off
+.icon-resize-horizontal { background-position: -456px -118px; } // 1px, 2px off
+
+.icon-hdd { background-position: 0 -144px; }
+.icon-bullhorn { background-position: -24px -144px; }
+.icon-bell { background-position: -48px -144px; }
+.icon-certificate { background-position: -72px -144px; }
+.icon-thumbs-up { background-position: -96px -144px; }
+.icon-thumbs-down { background-position: -120px -144px; }
+.icon-hand-right { background-position: -144px -144px; }
+.icon-hand-left { background-position: -168px -144px; }
+.icon-hand-up { background-position: -192px -144px; }
+.icon-hand-down { background-position: -216px -144px; }
+.icon-circle-arrow-right { background-position: -240px -144px; }
+.icon-circle-arrow-left { background-position: -264px -144px; }
+.icon-circle-arrow-up { background-position: -288px -144px; }
+.icon-circle-arrow-down { background-position: -312px -144px; }
+.icon-globe { background-position: -336px -144px; }
+.icon-wrench { background-position: -360px -144px; }
+.icon-tasks { background-position: -384px -144px; }
+.icon-filter { background-position: -408px -144px; }
+.icon-briefcase { background-position: -432px -144px; }
+.icon-fullscreen { background-position: -456px -144px; }
diff --git a/bootstrap/less/tables.less b/bootstrap/less/tables.less
new file mode 100644
index 0000000..13c37c6
--- /dev/null
+++ b/bootstrap/less/tables.less
@@ -0,0 +1,245 @@
+//
+// Tables
+// --------------------------------------------------
+
+
+// BASE TABLES
+// -----------------
+
+table {
+ max-width: 100%;
+ background-color: @tableBackground;
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+// BASELINE STYLES
+// ---------------
+
+.table {
+ width: 100%;
+ margin-bottom: @baseLineHeight;
+ // Cells
+ th,
+ td {
+ padding: 8px;
+ line-height: @baseLineHeight;
+ text-align: left;
+ vertical-align: top;
+ border-top: 1px solid @tableBorder;
+ }
+ th {
+ font-weight: bold;
+ }
+ // Bottom align for column headings
+ thead th {
+ vertical-align: bottom;
+ }
+ // Remove top border from thead by default
+ caption + thead tr:first-child th,
+ caption + thead tr:first-child td,
+ colgroup + thead tr:first-child th,
+ colgroup + thead tr:first-child td,
+ thead:first-child tr:first-child th,
+ thead:first-child tr:first-child td {
+ border-top: 0;
+ }
+ // Account for multiple tbody instances
+ tbody + tbody {
+ border-top: 2px solid @tableBorder;
+ }
+}
+
+
+
+// CONDENSED TABLE W/ HALF PADDING
+// -------------------------------
+
+.table-condensed {
+ th,
+ td {
+ padding: 4px 5px;
+ }
+}
+
+
+// BORDERED VERSION
+// ----------------
+
+.table-bordered {
+ border: 1px solid @tableBorder;
+ border-collapse: separate; // Done so we can round those corners!
+ *border-collapse: collapse; // IE7 can't round corners anyway
+ border-left: 0;
+ .border-radius(4px);
+ th,
+ td {
+ border-left: 1px solid @tableBorder;
+ }
+ // Prevent a double border
+ caption + thead tr:first-child th,
+ caption + tbody tr:first-child th,
+ caption + tbody tr:first-child td,
+ colgroup + thead tr:first-child th,
+ colgroup + tbody tr:first-child th,
+ colgroup + tbody tr:first-child td,
+ thead:first-child tr:first-child th,
+ tbody:first-child tr:first-child th,
+ tbody:first-child tr:first-child td {
+ border-top: 0;
+ }
+ // For first th or td in the first row in the first thead or tbody
+ thead:first-child tr:first-child th:first-child,
+ tbody:first-child tr:first-child td:first-child {
+ -webkit-border-top-left-radius: 4px;
+ border-top-left-radius: 4px;
+ -moz-border-radius-topleft: 4px;
+ }
+ thead:first-child tr:first-child th:last-child,
+ tbody:first-child tr:first-child td:last-child {
+ -webkit-border-top-right-radius: 4px;
+ border-top-right-radius: 4px;
+ -moz-border-radius-topright: 4px;
+ }
+ // For first th or td in the first row in the first thead or tbody
+ thead:last-child tr:last-child th:first-child,
+ tbody:last-child tr:last-child td:first-child,
+ tfoot:last-child tr:last-child td:first-child {
+ .border-radius(0 0 0 4px);
+ -webkit-border-bottom-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+ -moz-border-radius-bottomleft: 4px;
+ }
+ thead:last-child tr:last-child th:last-child,
+ tbody:last-child tr:last-child td:last-child,
+ tfoot:last-child tr:last-child td:last-child {
+ -webkit-border-bottom-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+ -moz-border-radius-bottomright: 4px;
+ }
+
+ // Special fixes to round the left border on the first td/th
+ caption + thead tr:first-child th:first-child,
+ caption + tbody tr:first-child td:first-child,
+ colgroup + thead tr:first-child th:first-child,
+ colgroup + tbody tr:first-child td:first-child {
+ -webkit-border-top-left-radius: 4px;
+ border-top-left-radius: 4px;
+ -moz-border-radius-topleft: 4px;
+ }
+ caption + thead tr:first-child th:last-child,
+ caption + tbody tr:first-child td:last-child,
+ colgroup + thead tr:first-child th:last-child,
+ colgroup + tbody tr:first-child td:last-child {
+ -webkit-border-top-right-radius: 4px;
+ border-top-right-radius: 4px;
+ -moz-border-radius-topleft: 4px;
+ }
+
+}
+
+
+
+
+// ZEBRA-STRIPING
+// --------------
+
+// Default zebra-stripe styles (alternating gray and transparent backgrounds)
+.table-striped {
+ tbody {
+ tr:nth-child(odd) td,
+ tr:nth-child(odd) th {
+ background-color: @tableBackgroundAccent;
+ }
+ }
+}
+
+
+// HOVER EFFECT
+// ------------
+// Placed here since it has to come after the potential zebra striping
+.table-hover {
+ tbody {
+ tr:hover td,
+ tr:hover th {
+ background-color: @tableBackgroundHover;
+ }
+ }
+}
+
+
+// TABLE CELL SIZING
+// -----------------
+
+// Reset default grid behavior
+table [class*=span],
+.row-fluid table [class*=span] {
+ display: table-cell;
+ float: none; // undo default grid column styles
+ margin-left: 0; // undo default grid column styles
+}
+
+// Change the column widths to account for td/th padding
+.table {
+ .span1 { .tableColumns(1); }
+ .span2 { .tableColumns(2); }
+ .span3 { .tableColumns(3); }
+ .span4 { .tableColumns(4); }
+ .span5 { .tableColumns(5); }
+ .span6 { .tableColumns(6); }
+ .span7 { .tableColumns(7); }
+ .span8 { .tableColumns(8); }
+ .span9 { .tableColumns(9); }
+ .span10 { .tableColumns(10); }
+ .span11 { .tableColumns(11); }
+ .span12 { .tableColumns(12); }
+ .span13 { .tableColumns(13); }
+ .span14 { .tableColumns(14); }
+ .span15 { .tableColumns(15); }
+ .span16 { .tableColumns(16); }
+ .span17 { .tableColumns(17); }
+ .span18 { .tableColumns(18); }
+ .span19 { .tableColumns(19); }
+ .span20 { .tableColumns(20); }
+ .span21 { .tableColumns(21); }
+ .span22 { .tableColumns(22); }
+ .span23 { .tableColumns(23); }
+ .span24 { .tableColumns(24); }
+}
+
+
+
+// TABLE BACKGROUNDS
+// -----------------
+// Exact selectors below required to override .table-striped
+
+.table tbody tr {
+ &.success td {
+ background-color: @successBackground;
+ }
+ &.error td {
+ background-color: @errorBackground;
+ }
+ &.warning td {
+ background-color: @warningBackground;
+ }
+ &.info td {
+ background-color: @infoBackground;
+ }
+}
+
+// Hover states for .table-hover
+.table-hover tbody tr {
+ &.success:hover td {
+ background-color: darken(@successBackground, 5%);
+ }
+ &.error:hover td {
+ background-color: darken(@errorBackground, 5%);
+ }
+ &.warning:hover td {
+ background-color: darken(@warningBackground, 5%);
+ }
+ &.info:hover td {
+ background-color: darken(@infoBackground, 5%);
+ }
+}
diff --git a/bootstrap/less/thumbnails.less b/bootstrap/less/thumbnails.less
new file mode 100644
index 0000000..91b75f7
--- /dev/null
+++ b/bootstrap/less/thumbnails.less
@@ -0,0 +1,52 @@
+//
+// Thumbnails
+// --------------------------------------------------
+
+
+// Note: `.thumbnails` and `.thumbnails > li` are overriden in responsive files
+
+// Make wrapper ul behave like the grid
+.thumbnails {
+ margin-left: -@gridGutterWidth;
+ list-style: none;
+ .clearfix();
+}
+// Fluid rows have no left margin
+.row-fluid .thumbnails {
+ margin-left: 0;
+}
+
+// Float li to make thumbnails appear in a row
+.thumbnails > li {
+ float: left; // Explicity set the float since we don't require .span* classes
+ margin-bottom: @baseLineHeight;
+ margin-left: @gridGutterWidth;
+}
+
+// The actual thumbnail (can be `a` or `div`)
+.thumbnail {
+ display: block;
+ padding: 4px;
+ line-height: @baseLineHeight;
+ border: 1px solid #ddd;
+ .border-radius(4px);
+ .box-shadow(0 1px 3px rgba(0,0,0,.055));
+ .transition(all .2s ease-in-out);
+}
+// Add a hover state for linked versions only
+a.thumbnail:hover {
+ border-color: @linkColor;
+ .box-shadow(0 1px 4px rgba(0,105,214,.25));
+}
+
+// Images and captions
+.thumbnail > img {
+ display: block;
+ max-width: 100%;
+ margin-left: auto;
+ margin-right: auto;
+}
+.thumbnail .caption {
+ padding: 9px;
+ color: @gray;
+}
diff --git a/bootstrap/less/tooltip.less b/bootstrap/less/tooltip.less
new file mode 100644
index 0000000..fba1856
--- /dev/null
+++ b/bootstrap/less/tooltip.less
@@ -0,0 +1,70 @@
+//
+// Tooltips
+// --------------------------------------------------
+
+
+// Base class
+.tooltip {
+ position: absolute;
+ z-index: @zindexTooltip;
+ display: block;
+ visibility: visible;
+ padding: 5px;
+ font-size: 11px;
+ .opacity(0);
+ &.in { .opacity(80); }
+ &.top { margin-top: -3px; }
+ &.right { margin-left: 3px; }
+ &.bottom { margin-top: 3px; }
+ &.left { margin-left: -3px; }
+}
+
+// Wrapper for the tooltip content
+.tooltip-inner {
+ max-width: 200px;
+ padding: 3px 8px;
+ color: @tooltipColor;
+ text-align: center;
+ text-decoration: none;
+ background-color: @tooltipBackground;
+ .border-radius(4px);
+}
+
+// Arrows
+.tooltip-arrow {
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+}
+.tooltip {
+ &.top .tooltip-arrow {
+ bottom: 0;
+ left: 50%;
+ margin-left: -@tooltipArrowWidth;
+ border-width: @tooltipArrowWidth @tooltipArrowWidth 0;
+ border-top-color: @tooltipArrowColor;
+ }
+ &.right .tooltip-arrow {
+ top: 50%;
+ left: 0;
+ margin-top: -@tooltipArrowWidth;
+ border-width: @tooltipArrowWidth @tooltipArrowWidth @tooltipArrowWidth 0;
+ border-right-color: @tooltipArrowColor;
+ }
+ &.left .tooltip-arrow {
+ top: 50%;
+ right: 0;
+ margin-top: -@tooltipArrowWidth;
+ border-width: @tooltipArrowWidth 0 @tooltipArrowWidth @tooltipArrowWidth;
+ border-left-color: @tooltipArrowColor;
+ }
+ &.bottom .tooltip-arrow {
+ top: 0;
+ left: 50%;
+ margin-left: -@tooltipArrowWidth;
+ border-width: 0 @tooltipArrowWidth @tooltipArrowWidth;
+ border-bottom-color: @tooltipArrowColor;
+ }
+}
diff --git a/bootstrap/less/type.less b/bootstrap/less/type.less
new file mode 100644
index 0000000..2e0f386
--- /dev/null
+++ b/bootstrap/less/type.less
@@ -0,0 +1,221 @@
+//
+// Typography
+// --------------------------------------------------
+
+
+// Body text
+// -------------------------
+
+p {
+ margin: 0 0 @baseLineHeight / 2;
+}
+.lead {
+ margin-bottom: @baseLineHeight;
+ font-size: @baseFontSize * 1.5;
+ font-weight: 200;
+ line-height: @baseLineHeight * 1.5;
+}
+
+
+// Emphasis & misc
+// -------------------------
+
+small {
+ font-size: 85%; // Ex: 14px base font * 85% = about 12px
+}
+strong {
+ font-weight: bold;
+}
+em {
+ font-style: italic;
+}
+cite {
+ font-style: normal;
+}
+
+// Utility classes
+.muted {
+ color: @grayLight;
+}
+.text-warning {
+ color: @warningText;
+}
+.text-error {
+ color: @errorText;
+}
+.text-info {
+ color: @infoText;
+}
+.text-success {
+ color: @successText;
+}
+
+
+// Headings
+// -------------------------
+
+h1, h2, h3, h4, h5, h6 {
+ margin: (@baseLineHeight / 2) 0;
+ font-family: @headingsFontFamily;
+ font-weight: @headingsFontWeight;
+ line-height: 1;
+ color: @headingsColor;
+ text-rendering: optimizelegibility; // Fix the character spacing for headings
+ small {
+ font-weight: normal;
+ line-height: 1;
+ color: @grayLight;
+ }
+}
+h1 { font-size: 36px; line-height: 40px; }
+h2 { font-size: 30px; line-height: 40px; }
+h3 { font-size: 24px; line-height: 40px; }
+h4 { font-size: 18px; line-height: 20px; }
+h5 { font-size: 14px; line-height: 20px; }
+h6 { font-size: 12px; line-height: 20px; }
+
+h1 small { font-size: 24px; }
+h2 small { font-size: 18px; }
+h3 small { font-size: 14px; }
+h4 small { font-size: 14px; }
+
+
+// Page header
+// -------------------------
+
+.page-header {
+ padding-bottom: (@baseLineHeight / 2) - 1;
+ margin: @baseLineHeight 0 (@baseLineHeight * 1.5);
+ border-bottom: 1px solid @grayLighter;
+}
+
+
+
+// Lists
+// --------------------------------------------------
+
+// Unordered and Ordered lists
+ul, ol {
+ padding: 0;
+ margin: 0 0 @baseLineHeight / 2 25px;
+}
+ul ul,
+ul ol,
+ol ol,
+ol ul {
+ margin-bottom: 0;
+}
+li {
+ line-height: @baseLineHeight;
+}
+ul.unstyled,
+ol.unstyled {
+ margin-left: 0;
+ list-style: none;
+}
+
+// Description Lists
+dl {
+ margin-bottom: @baseLineHeight;
+}
+dt,
+dd {
+ line-height: @baseLineHeight;
+}
+dt {
+ font-weight: bold;
+}
+dd {
+ margin-left: @baseLineHeight / 2;
+}
+// Horizontal layout (like forms)
+.dl-horizontal {
+ .clearfix(); // Ensure dl clears floats if empty dd elements present
+ dt {
+ float: left;
+ width: @horizontalComponentOffset - 20;
+ clear: left;
+ text-align: right;
+ .text-overflow();
+ }
+ dd {
+ margin-left: @horizontalComponentOffset;
+ }
+}
+
+// MISC
+// ----
+
+// Horizontal rules
+hr {
+ margin: @baseLineHeight 0;
+ border: 0;
+ border-top: 1px solid @hrBorder;
+ border-bottom: 1px solid @white;
+}
+
+// Abbreviations and acronyms
+abbr[title] {
+ cursor: help;
+ border-bottom: 1px dotted @grayLight;
+}
+abbr.initialism {
+ font-size: 90%;
+ text-transform: uppercase;
+}
+
+// Blockquotes
+blockquote {
+ padding: 0 0 0 15px;
+ margin: 0 0 @baseLineHeight;
+ border-left: 5px solid @grayLighter;
+ p {
+ margin-bottom: 0;
+ #font > .shorthand(16px,300,@baseLineHeight * 1.25);
+ }
+ small {
+ display: block;
+ line-height: @baseLineHeight;
+ color: @grayLight;
+ &:before {
+ content: '\2014 \00A0';
+ }
+ }
+
+ // Float right with text-align: right
+ &.pull-right {
+ float: right;
+ padding-right: 15px;
+ padding-left: 0;
+ border-right: 5px solid @grayLighter;
+ border-left: 0;
+ p,
+ small {
+ text-align: right;
+ }
+ small {
+ &:before {
+ content: '';
+ }
+ &:after {
+ content: '\00A0 \2014';
+ }
+ }
+ }
+}
+
+// Quotes
+q:before,
+q:after,
+blockquote:before,
+blockquote:after {
+ content: "";
+}
+
+// Addresses
+address {
+ display: block;
+ margin-bottom: @baseLineHeight;
+ font-style: normal;
+ line-height: @baseLineHeight;
+}
diff --git a/bootstrap/less/utilities.less b/bootstrap/less/utilities.less
new file mode 100644
index 0000000..314b4ff
--- /dev/null
+++ b/bootstrap/less/utilities.less
@@ -0,0 +1,30 @@
+//
+// Utility classes
+// --------------------------------------------------
+
+
+// Quick floats
+.pull-right {
+ float: right;
+}
+.pull-left {
+ float: left;
+}
+
+// Toggling content
+.hide {
+ display: none;
+}
+.show {
+ display: block;
+}
+
+// Visibility
+.invisible {
+ visibility: hidden;
+}
+
+// For Affix plugin
+.affix {
+ position: fixed;
+}
diff --git a/bootstrap/less/variables.less b/bootstrap/less/variables.less
new file mode 100644
index 0000000..542aa2c
--- /dev/null
+++ b/bootstrap/less/variables.less
@@ -0,0 +1,279 @@
+//
+// Variables
+// --------------------------------------------------
+
+
+// Global values
+// --------------------------------------------------
+
+
+// Grays
+// -------------------------
+@black: #000;
+@grayDarker: #222;
+@grayDark: #333;
+@gray: #555;
+@grayLight: #999;
+@grayLighter: #eee;
+@white: #fff;
+
+
+// Accent colors
+// -------------------------
+@blue: #049cdb;
+@blueDark: #0064cd;
+@green: #46a546;
+@red: #9d261d;
+@yellow: #ffc40d;
+@orange: #f89406;
+@pink: #c3325f;
+@purple: #7a43b6;
+
+
+// Scaffolding
+// -------------------------
+@bodyBackground: @white;
+@textColor: #5F5F5F;
+
+
+// Links
+// -------------------------
+@linkColor: #08c;
+@linkColorHover: darken(@linkColor, 15%);
+
+
+// Typography
+// -------------------------
+@sansFontFamily: "Helvetica Neue", Helvetica, Arial, sans-serif;
+@serifFontFamily: Georgia, "Times New Roman", Times, serif;
+@monoFontFamily: Monaco, Menlo, Consolas, "Courier New", monospace;
+
+@baseFontSize: 12px;
+@baseFontFamily: @sansFontFamily;
+@baseLineHeight: 22px;
+@altFontFamily: @serifFontFamily;
+
+@headingsFontFamily: inherit; // empty to use BS default, @baseFontFamily
+@headingsFontWeight: bold; // instead of browser default, bold
+@headingsColor: inherit; // empty to use BS default, @textColor
+
+// Tables
+// -------------------------
+@tableBackground: transparent; // overall background-color
+@tableBackgroundAccent: #f9f9f9; // for striping
+@tableBackgroundHover: #f5f5f5; // for hover
+@tableBorder: #ddd; // table and cell border
+
+// Buttons
+// -------------------------
+@btnBackground: @white;
+@btnBackgroundHighlight: darken(@white, 10%);
+@btnBorder: #bbb;
+
+@btnPrimaryBackground: @linkColor;
+@btnPrimaryBackgroundHighlight: spin(@btnPrimaryBackground, 20%);
+
+@btnInfoBackground: #5bc0de;
+@btnInfoBackgroundHighlight: #2f96b4;
+
+@btnSuccessBackground: #62c462;
+@btnSuccessBackgroundHighlight: #51a351;
+
+@btnWarningBackground: lighten(@orange, 15%);
+@btnWarningBackgroundHighlight: @orange;
+
+@btnDangerBackground: #ee5f5b;
+@btnDangerBackgroundHighlight: #bd362f;
+
+@btnInverseBackground: #444;
+@btnInverseBackgroundHighlight: @grayDarker;
+
+
+// Forms
+// -------------------------
+@inputBackground: #fdfdfd;
+@inputBorder: #ccc;
+@inputBorderRadius: 3px;
+@inputDisabledBackground: @grayLighter;
+@formActionsBackground: #f5f5f5;
+
+// Dropdowns
+// -------------------------
+@dropdownBackground: @white;
+@dropdownBorder: rgba(0,0,0,.2);
+@dropdownDividerTop: #e5e5e5;
+@dropdownDividerBottom: @white;
+
+@dropdownLinkColor: @grayDark;
+@dropdownLinkColorHover: @white;
+@dropdownLinkColorActive: @dropdownLinkColor;
+
+@dropdownLinkBackgroundActive: @linkColor;
+@dropdownLinkBackgroundHover: @dropdownLinkBackgroundActive;
+
+
+
+// COMPONENT VARIABLES
+// --------------------------------------------------
+
+// Z-index master list
+// -------------------------
+// Used for a bird's eye view of components dependent on the z-axis
+// Try to avoid customizing these :)
+@zindexDropdown: 1000;
+@zindexPopover: 1010;
+@zindexTooltip: 1030;
+@zindexFixedNavbar: 1030;
+@zindexModalBackdrop: 1040;
+@zindexModal: 1050;
+
+
+// Sprite icons path
+// -------------------------
+@iconSpritePath: "../img/glyphicons-halflings.png";
+@iconWhiteSpritePath: "../img/glyphicons-halflings-white.png";
+
+
+// Input placeholder text color
+// -------------------------
+@placeholderText: @grayLight;
+
+
+// Hr border color
+// -------------------------
+@hrBorder: @grayLighter;
+
+
+// Horizontal forms & lists
+// -------------------------
+@horizontalComponentOffset: 180px;
+
+
+// Wells
+// -------------------------
+@wellBackground: #f5f5f5;
+
+
+// Navbar
+// -------------------------
+@navbarCollapseWidth: 979px;
+
+@navbarHeight: 40px;
+@navbarBackgroundHighlight: #ffffff;
+@navbarBackground: darken(@navbarBackgroundHighlight, 5%);
+@navbarBorder: darken(@navbarBackground, 12%);
+
+@navbarText: #777;
+@navbarLinkColor: #777;
+@navbarLinkColorHover: @grayDark;
+@navbarLinkColorActive: @gray;
+@navbarLinkBackgroundHover: transparent;
+@navbarLinkBackgroundActive: darken(@navbarBackground, 5%);
+
+@navbarBrandColor: @navbarLinkColor;
+
+// Inverted navbar
+@navbarInverseBackground: #111111;
+@navbarInverseBackgroundHighlight: #222222;
+@navbarInverseBorder: #252525;
+
+@navbarInverseText: @grayLight;
+@navbarInverseLinkColor: @grayLight;
+@navbarInverseLinkColorHover: @white;
+@navbarInverseLinkColorActive: @navbarInverseLinkColorHover;
+@navbarInverseLinkBackgroundHover: transparent;
+@navbarInverseLinkBackgroundActive: @navbarInverseBackground;
+
+@navbarInverseSearchBackground: lighten(@navbarInverseBackground, 25%);
+@navbarInverseSearchBackgroundFocus: @white;
+@navbarInverseSearchBorder: @navbarInverseBackground;
+@navbarInverseSearchPlaceholderColor: #ccc;
+
+@navbarInverseBrandColor: @navbarInverseLinkColor;
+
+
+// Pagination
+// -------------------------
+@paginationBackground: #fff;
+@paginationBorder: #ddd;
+@paginationActiveBackground: #f5f5f5;
+
+
+// Hero unit
+// -------------------------
+@heroUnitBackground: @grayLighter;
+@heroUnitHeadingColor: inherit;
+@heroUnitLeadColor: inherit;
+
+
+// Form states and alerts
+// -------------------------
+@warningText: #c09853;
+@warningBackground: #fcf8e3;
+@warningBorder: darken(spin(@warningBackground, -10), 3%);
+
+@errorText: #b94a48;
+@errorBackground: #f2dede;
+@errorBorder: darken(spin(@errorBackground, -10), 3%);
+
+@successText: #468847;
+@successBackground: #dff0d8;
+@successBorder: darken(spin(@successBackground, -10), 5%);
+
+@infoText: #3a87ad;
+@infoBackground: #d9edf7;
+@infoBorder: darken(spin(@infoBackground, -10), 7%);
+
+
+// Tooltips and popovers
+// -------------------------
+@tooltipColor: #fff;
+@tooltipBackground: #000;
+@tooltipArrowWidth: 5px;
+@tooltipArrowColor: @tooltipBackground;
+
+@popoverBackground: #fff;
+@popoverArrowWidth: 10px;
+@popoverArrowColor: #fff;
+@popoverTitleBackground: darken(@popoverBackground, 3%);
+
+// Special enhancement for popovers
+@popoverArrowOuterWidth: @popoverArrowWidth + 1;
+@popoverArrowOuterColor: rgba(0,0,0,.25);
+
+
+
+// GRID
+// --------------------------------------------------
+
+
+// Default 940px grid
+// -------------------------
+@gridColumns: 12;
+@gridColumnWidth: 60px;
+@gridGutterWidth: 20px;
+@gridRowWidth: (@gridColumns * @gridColumnWidth) + (@gridGutterWidth * (@gridColumns - 1));
+
+// 1200px min
+@gridColumnWidth1200: 70px;
+@gridGutterWidth1200: 30px;
+@gridRowWidth1200: (@gridColumns * @gridColumnWidth1200) + (@gridGutterWidth1200 * (@gridColumns - 1));
+
+// 768px-979px
+@gridColumnWidth768: 42px;
+@gridGutterWidth768: 20px;
+@gridRowWidth768: (@gridColumns * @gridColumnWidth768) + (@gridGutterWidth768 * (@gridColumns - 1));
+
+
+// Fluid grid
+// -------------------------
+@fluidGridColumnWidth: percentage(@gridColumnWidth/@gridRowWidth);
+@fluidGridGutterWidth: percentage(@gridGutterWidth/@gridRowWidth);
+
+// 1200px min
+@fluidGridColumnWidth1200: percentage(@gridColumnWidth1200/@gridRowWidth1200);
+@fluidGridGutterWidth1200: percentage(@gridGutterWidth1200/@gridRowWidth1200);
+
+// 768px-979px
+@fluidGridColumnWidth768: percentage(@gridColumnWidth768/@gridRowWidth768);
+@fluidGridGutterWidth768: percentage(@gridGutterWidth768/@gridRowWidth768);
diff --git a/bootstrap/less/wells.less b/bootstrap/less/wells.less
new file mode 100644
index 0000000..e4e0a9b
--- /dev/null
+++ b/bootstrap/less/wells.less
@@ -0,0 +1,29 @@
+//
+// Wells
+// --------------------------------------------------
+
+
+// Base class
+.well {
+ min-height: 20px;
+ padding: 19px;
+ margin-bottom: 20px;
+ background-color: @wellBackground;
+ border: 1px solid darken(@wellBackground, 7%);
+ .border-radius(4px);
+ .box-shadow(inset 0 1px 1px rgba(0,0,0,.05));
+ blockquote {
+ border-color: #ddd;
+ border-color: rgba(0,0,0,.15);
+ }
+}
+
+// Sizes
+.well-large {
+ padding: 24px;
+ .border-radius(6px);
+}
+.well-small {
+ padding: 9px;
+ .border-radius(3px);
+}
diff --git a/bootstrap/tmp/bootstrap.js b/bootstrap/tmp/bootstrap.js
new file mode 100644
index 0000000..f73fcb8
--- /dev/null
+++ b/bootstrap/tmp/bootstrap.js
@@ -0,0 +1,2027 @@
+/* ===================================================
+ * bootstrap-transition.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#transitions
+ * ===================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ $(function () {
+
+ "use strict"; // jshint ;_;
+
+
+ /* CSS TRANSITION SUPPORT (http://www.modernizr.com/)
+ * ======================================================= */
+
+ $.support.transition = (function () {
+
+ var transitionEnd = (function () {
+
+ var el = document.createElement('bootstrap')
+ , transEndEventNames = {
+ 'WebkitTransition' : 'webkitTransitionEnd'
+ , 'MozTransition' : 'transitionend'
+ , 'OTransition' : 'oTransitionEnd otransitionend'
+ , 'transition' : 'transitionend'
+ }
+ , name
+
+ for (name in transEndEventNames){
+ if (el.style[name] !== undefined) {
+ return transEndEventNames[name]
+ }
+ }
+
+ }())
+
+ return transitionEnd && {
+ end: transitionEnd
+ }
+
+ })()
+
+ })
+
+}(window.jQuery);/* ==========================================================
+ * bootstrap-alert.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#alerts
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* ALERT CLASS DEFINITION
+ * ====================== */
+
+ var dismiss = '[data-dismiss="alert"]'
+ , Alert = function (el) {
+ $(el).on('click', dismiss, this.close)
+ }
+
+ Alert.prototype.close = function (e) {
+ var $this = $(this)
+ , selector = $this.attr('data-target')
+ , $parent
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+ }
+
+ $parent = $(selector)
+
+ e && e.preventDefault()
+
+ $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent())
+
+ $parent.trigger(e = $.Event('close'))
+
+ if (e.isDefaultPrevented()) return
+
+ $parent.removeClass('in')
+
+ function removeElement() {
+ $parent
+ .trigger('closed')
+ .remove()
+ }
+
+ $.support.transition && $parent.hasClass('fade') ?
+ $parent.on($.support.transition.end, removeElement) :
+ removeElement()
+ }
+
+
+ /* ALERT PLUGIN DEFINITION
+ * ======================= */
+
+ $.fn.alert = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('alert')
+ if (!data) $this.data('alert', (data = new Alert(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ $.fn.alert.Constructor = Alert
+
+
+ /* ALERT DATA-API
+ * ============== */
+
+ $(function () {
+ $('body').on('click.alert.data-api', dismiss, Alert.prototype.close)
+ })
+
+}(window.jQuery);/* ============================================================
+ * bootstrap-button.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#buttons
+ * ============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* BUTTON PUBLIC CLASS DEFINITION
+ * ============================== */
+
+ var Button = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, $.fn.button.defaults, options)
+ }
+
+ Button.prototype.setState = function (state) {
+ var d = 'disabled'
+ , $el = this.$element
+ , data = $el.data()
+ , val = $el.is('input') ? 'val' : 'html'
+
+ state = state + 'Text'
+ data.resetText || $el.data('resetText', $el[val]())
+
+ $el[val](data[state] || this.options[state])
+
+ // push to event loop to allow forms to submit
+ setTimeout(function () {
+ state == 'loadingText' ?
+ $el.addClass(d).attr(d, d) :
+ $el.removeClass(d).removeAttr(d)
+ }, 0)
+ }
+
+ Button.prototype.toggle = function () {
+ var $parent = this.$element.closest('[data-toggle="buttons-radio"]')
+
+ $parent && $parent
+ .find('.active')
+ .removeClass('active')
+
+ this.$element.toggleClass('active')
+ }
+
+
+ /* BUTTON PLUGIN DEFINITION
+ * ======================== */
+
+ $.fn.button = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('button')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('button', (data = new Button(this, options)))
+ if (option == 'toggle') data.toggle()
+ else if (option) data.setState(option)
+ })
+ }
+
+ $.fn.button.defaults = {
+ loadingText: 'loading...'
+ }
+
+ $.fn.button.Constructor = Button
+
+
+ /* BUTTON DATA-API
+ * =============== */
+
+ $(function () {
+ $('body').on('click.button.data-api', '[data-toggle^=button]', function ( e ) {
+ var $btn = $(e.target)
+ if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
+ $btn.button('toggle')
+ })
+ })
+
+}(window.jQuery);/* ==========================================================
+ * bootstrap-carousel.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#carousel
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* CAROUSEL CLASS DEFINITION
+ * ========================= */
+
+ var Carousel = function (element, options) {
+ this.$element = $(element)
+ this.options = options
+ this.options.slide && this.slide(this.options.slide)
+ this.options.pause == 'hover' && this.$element
+ .on('mouseenter', $.proxy(this.pause, this))
+ .on('mouseleave', $.proxy(this.cycle, this))
+ }
+
+ Carousel.prototype = {
+
+ cycle: function (e) {
+ if (!e) this.paused = false
+ this.options.interval
+ && !this.paused
+ && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
+ return this
+ }
+
+ , to: function (pos) {
+ var $active = this.$element.find('.item.active')
+ , children = $active.parent().children()
+ , activePos = children.index($active)
+ , that = this
+
+ if (pos > (children.length - 1) || pos < 0) return
+
+ if (this.sliding) {
+ return this.$element.one('slid', function () {
+ that.to(pos)
+ })
+ }
+
+ if (activePos == pos) {
+ return this.pause().cycle()
+ }
+
+ return this.slide(pos > activePos ? 'next' : 'prev', $(children[pos]))
+ }
+
+ , pause: function (e) {
+ if (!e) this.paused = true
+ if (this.$element.find('.next, .prev').length && $.support.transition.end) {
+ this.$element.trigger($.support.transition.end)
+ this.cycle()
+ }
+ clearInterval(this.interval)
+ this.interval = null
+ return this
+ }
+
+ , next: function () {
+ if (this.sliding) return
+ return this.slide('next')
+ }
+
+ , prev: function () {
+ if (this.sliding) return
+ return this.slide('prev')
+ }
+
+ , slide: function (type, next) {
+ var $active = this.$element.find('.item.active')
+ , $next = next || $active[type]()
+ , isCycling = this.interval
+ , direction = type == 'next' ? 'left' : 'right'
+ , fallback = type == 'next' ? 'first' : 'last'
+ , that = this
+ , e = $.Event('slide', {
+ relatedTarget: $next[0]
+ })
+
+ this.sliding = true
+
+ isCycling && this.pause()
+
+ $next = $next.length ? $next : this.$element.find('.item')[fallback]()
+
+ if ($next.hasClass('active')) return
+
+ if ($.support.transition && this.$element.hasClass('slide')) {
+ this.$element.trigger(e)
+ if (e.isDefaultPrevented()) return
+ $next.addClass(type)
+ $next[0].offsetWidth // force reflow
+ $active.addClass(direction)
+ $next.addClass(direction)
+ this.$element.one($.support.transition.end, function () {
+ $next.removeClass([type, direction].join(' ')).addClass('active')
+ $active.removeClass(['active', direction].join(' '))
+ that.sliding = false
+ setTimeout(function () { that.$element.trigger('slid') }, 0)
+ })
+ } else {
+ this.$element.trigger(e)
+ if (e.isDefaultPrevented()) return
+ $active.removeClass('active')
+ $next.addClass('active')
+ this.sliding = false
+ this.$element.trigger('slid')
+ }
+
+ isCycling && this.cycle()
+
+ return this
+ }
+
+ }
+
+
+ /* CAROUSEL PLUGIN DEFINITION
+ * ========================== */
+
+ $.fn.carousel = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('carousel')
+ , options = $.extend({}, $.fn.carousel.defaults, typeof option == 'object' && option)
+ , action = typeof option == 'string' ? option : options.slide
+ if (!data) $this.data('carousel', (data = new Carousel(this, options)))
+ if (typeof option == 'number') data.to(option)
+ else if (action) data[action]()
+ else if (options.interval) data.cycle()
+ })
+ }
+
+ $.fn.carousel.defaults = {
+ interval: 5000
+ , pause: 'hover'
+ }
+
+ $.fn.carousel.Constructor = Carousel
+
+
+ /* CAROUSEL DATA-API
+ * ================= */
+
+ $(function () {
+ $('body').on('click.carousel.data-api', '[data-slide]', function ( e ) {
+ var $this = $(this), href
+ , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+ , options = !$target.data('modal') && $.extend({}, $target.data(), $this.data())
+ $target.carousel(options)
+ e.preventDefault()
+ })
+ })
+
+}(window.jQuery);/* =============================================================
+ * bootstrap-collapse.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#collapse
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* COLLAPSE PUBLIC CLASS DEFINITION
+ * ================================ */
+
+ var Collapse = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, $.fn.collapse.defaults, options)
+
+ if (this.options.parent) {
+ this.$parent = $(this.options.parent)
+ }
+
+ this.options.toggle && this.toggle()
+ }
+
+ Collapse.prototype = {
+
+ constructor: Collapse
+
+ , dimension: function () {
+ var hasWidth = this.$element.hasClass('width')
+ return hasWidth ? 'width' : 'height'
+ }
+
+ , show: function () {
+ var dimension
+ , scroll
+ , actives
+ , hasData
+
+ if (this.transitioning) return
+
+ dimension = this.dimension()
+ scroll = $.camelCase(['scroll', dimension].join('-'))
+ actives = this.$parent && this.$parent.find('> .accordion-group > .in')
+
+ if (actives && actives.length) {
+ hasData = actives.data('collapse')
+ if (hasData && hasData.transitioning) return
+ actives.collapse('hide')
+ hasData || actives.data('collapse', null)
+ }
+
+ this.$element[dimension](0)
+ this.transition('addClass', $.Event('show'), 'shown')
+ $.support.transition && this.$element[dimension](this.$element[0][scroll])
+ }
+
+ , hide: function () {
+ var dimension
+ if (this.transitioning) return
+ dimension = this.dimension()
+ this.reset(this.$element[dimension]())
+ this.transition('removeClass', $.Event('hide'), 'hidden')
+ this.$element[dimension](0)
+ }
+
+ , reset: function (size) {
+ var dimension = this.dimension()
+
+ this.$element
+ .removeClass('collapse')
+ [dimension](size || 'auto')
+ [0].offsetWidth
+
+ this.$element[size !== null ? 'addClass' : 'removeClass']('collapse')
+
+ return this
+ }
+
+ , transition: function (method, startEvent, completeEvent) {
+ var that = this
+ , complete = function () {
+ if (startEvent.type == 'show') that.reset()
+ that.transitioning = 0
+ that.$element.trigger(completeEvent)
+ }
+
+ this.$element.trigger(startEvent)
+
+ if (startEvent.isDefaultPrevented()) return
+
+ this.transitioning = 1
+
+ this.$element[method]('in')
+
+ $.support.transition && this.$element.hasClass('collapse') ?
+ this.$element.one($.support.transition.end, complete) :
+ complete()
+ }
+
+ , toggle: function () {
+ this[this.$element.hasClass('in') ? 'hide' : 'show']()
+ }
+
+ }
+
+
+ /* COLLAPSIBLE PLUGIN DEFINITION
+ * ============================== */
+
+ $.fn.collapse = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('collapse')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('collapse', (data = new Collapse(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.collapse.defaults = {
+ toggle: true
+ }
+
+ $.fn.collapse.Constructor = Collapse
+
+
+ /* COLLAPSIBLE DATA-API
+ * ==================== */
+
+ $(function () {
+ $('body').on('click.collapse.data-api', '[data-toggle=collapse]', function (e) {
+ var $this = $(this), href
+ , target = $this.attr('data-target')
+ || e.preventDefault()
+ || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
+ , option = $(target).data('collapse') ? 'toggle' : $this.data()
+ $this[$(target).hasClass('in') ? 'addClass' : 'removeClass']('collapsed')
+ $(target).collapse(option)
+ })
+ })
+
+}(window.jQuery);/* ============================================================
+ * bootstrap-dropdown.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#dropdowns
+ * ============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* DROPDOWN CLASS DEFINITION
+ * ========================= */
+
+ var toggle = '[data-toggle=dropdown]'
+ , Dropdown = function (element) {
+ var $el = $(element).on('click.dropdown.data-api', this.toggle)
+ $('html').on('click.dropdown.data-api', function () {
+ $el.parent().removeClass('open')
+ })
+ }
+
+ Dropdown.prototype = {
+
+ constructor: Dropdown
+
+ , toggle: function (e) {
+ var $this = $(this)
+ , $parent
+ , isActive
+
+ if ($this.is('.disabled, :disabled')) return
+
+ $parent = getParent($this)
+
+ isActive = $parent.hasClass('open')
+
+ clearMenus()
+
+ if (!isActive) {
+ $parent.toggleClass('open')
+ $this.focus()
+ }
+
+ return false
+ }
+
+ , keydown: function (e) {
+ var $this
+ , $items
+ , $active
+ , $parent
+ , isActive
+ , index
+
+ if (!/(38|40|27)/.test(e.keyCode)) return
+
+ $this = $(this)
+
+ e.preventDefault()
+ e.stopPropagation()
+
+ if ($this.is('.disabled, :disabled')) return
+
+ $parent = getParent($this)
+
+ isActive = $parent.hasClass('open')
+
+ if (!isActive || (isActive && e.keyCode == 27)) return $this.click()
+
+ $items = $('[role=menu] li:not(.divider) a', $parent)
+
+ if (!$items.length) return
+
+ index = $items.index($items.filter(':focus'))
+
+ if (e.keyCode == 38 && index > 0) index-- // up
+ if (e.keyCode == 40 && index < $items.length - 1) index++ // down
+ if (!~index) index = 0
+
+ $items
+ .eq(index)
+ .focus()
+ }
+
+ }
+
+ function clearMenus() {
+ getParent($(toggle))
+ .removeClass('open')
+ }
+
+ function getParent($this) {
+ var selector = $this.attr('data-target')
+ , $parent
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+ }
+
+ $parent = $(selector)
+ $parent.length || ($parent = $this.parent())
+
+ return $parent
+ }
+
+
+ /* DROPDOWN PLUGIN DEFINITION
+ * ========================== */
+
+ $.fn.dropdown = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('dropdown')
+ if (!data) $this.data('dropdown', (data = new Dropdown(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ $.fn.dropdown.Constructor = Dropdown
+
+
+ /* APPLY TO STANDARD DROPDOWN ELEMENTS
+ * =================================== */
+
+ $(function () {
+ $('html')
+ .on('click.dropdown.data-api touchstart.dropdown.data-api', clearMenus)
+ $('body')
+ .on('click.dropdown touchstart.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
+ .on('click.dropdown.data-api touchstart.dropdown.data-api' , toggle, Dropdown.prototype.toggle)
+ .on('keydown.dropdown.data-api touchstart.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown)
+ })
+
+}(window.jQuery);/* =========================================================
+ * bootstrap-modal.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#modals
+ * =========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================= */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* MODAL CLASS DEFINITION
+ * ====================== */
+
+ var Modal = function (element, options) {
+ this.options = options
+ this.$element = $(element)
+ .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this))
+ this.options.remote && this.$element.find('.modal-body').load(this.options.remote)
+ }
+
+ Modal.prototype = {
+
+ constructor: Modal
+
+ , toggle: function () {
+ return this[!this.isShown ? 'show' : 'hide']()
+ }
+
+ , show: function () {
+ var that = this
+ , e = $.Event('show')
+
+ this.$element.trigger(e)
+
+ if (this.isShown || e.isDefaultPrevented()) return
+
+ $('body').addClass('modal-open')
+
+ this.isShown = true
+
+ this.escape()
+
+ this.backdrop(function () {
+ var transition = $.support.transition && that.$element.hasClass('fade')
+
+ if (!that.$element.parent().length) {
+ that.$element.appendTo(document.body) //don't move modals dom position
+ }
+
+ that.$element
+ .show()
+
+ if (transition) {
+ that.$element[0].offsetWidth // force reflow
+ }
+
+ that.$element
+ .addClass('in')
+ .attr('aria-hidden', false)
+ .focus()
+
+ that.enforceFocus()
+
+ transition ?
+ that.$element.one($.support.transition.end, function () { that.$element.trigger('shown') }) :
+ that.$element.trigger('shown')
+
+ })
+ }
+
+ , hide: function (e) {
+ e && e.preventDefault()
+
+ var that = this
+
+ e = $.Event('hide')
+
+ this.$element.trigger(e)
+
+ if (!this.isShown || e.isDefaultPrevented()) return
+
+ this.isShown = false
+
+ $('body').removeClass('modal-open')
+
+ this.escape()
+
+ $(document).off('focusin.modal')
+
+ this.$element
+ .removeClass('in')
+ .attr('aria-hidden', true)
+
+ $.support.transition && this.$element.hasClass('fade') ?
+ this.hideWithTransition() :
+ this.hideModal()
+ }
+
+ , enforceFocus: function () {
+ var that = this
+ $(document).on('focusin.modal', function (e) {
+ if (that.$element[0] !== e.target && !that.$element.has(e.target).length) {
+ that.$element.focus()
+ }
+ })
+ }
+
+ , escape: function () {
+ var that = this
+ if (this.isShown && this.options.keyboard) {
+ this.$element.on('keyup.dismiss.modal', function ( e ) {
+ e.which == 27 && that.hide()
+ })
+ } else if (!this.isShown) {
+ this.$element.off('keyup.dismiss.modal')
+ }
+ }
+
+ , hideWithTransition: function () {
+ var that = this
+ , timeout = setTimeout(function () {
+ that.$element.off($.support.transition.end)
+ that.hideModal()
+ }, 500)
+
+ this.$element.one($.support.transition.end, function () {
+ clearTimeout(timeout)
+ that.hideModal()
+ })
+ }
+
+ , hideModal: function (that) {
+ this.$element
+ .hide()
+ .trigger('hidden')
+
+ this.backdrop()
+ }
+
+ , removeBackdrop: function () {
+ this.$backdrop.remove()
+ this.$backdrop = null
+ }
+
+ , backdrop: function (callback) {
+ var that = this
+ , animate = this.$element.hasClass('fade') ? 'fade' : ''
+
+ if (this.isShown && this.options.backdrop) {
+ var doAnimate = $.support.transition && animate
+
+ this.$backdrop = $('
')
+ .appendTo(document.body)
+
+ if (this.options.backdrop != 'static') {
+ this.$backdrop.click($.proxy(this.hide, this))
+ }
+
+ if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
+
+ this.$backdrop.addClass('in')
+
+ doAnimate ?
+ this.$backdrop.one($.support.transition.end, callback) :
+ callback()
+
+ } else if (!this.isShown && this.$backdrop) {
+ this.$backdrop.removeClass('in')
+
+ $.support.transition && this.$element.hasClass('fade')?
+ this.$backdrop.one($.support.transition.end, $.proxy(this.removeBackdrop, this)) :
+ this.removeBackdrop()
+
+ } else if (callback) {
+ callback()
+ }
+ }
+ }
+
+
+ /* MODAL PLUGIN DEFINITION
+ * ======================= */
+
+ $.fn.modal = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('modal')
+ , options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option)
+ if (!data) $this.data('modal', (data = new Modal(this, options)))
+ if (typeof option == 'string') data[option]()
+ else if (options.show) data.show()
+ })
+ }
+
+ $.fn.modal.defaults = {
+ backdrop: true
+ , keyboard: true
+ , show: true
+ }
+
+ $.fn.modal.Constructor = Modal
+
+
+ /* MODAL DATA-API
+ * ============== */
+
+ $(function () {
+ $('body').on('click.modal.data-api', '[data-toggle="modal"]', function ( e ) {
+ var $this = $(this)
+ , href = $this.attr('href')
+ , $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) //strip for ie7
+ , option = $target.data('modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
+
+ e.preventDefault()
+
+ $target
+ .modal(option)
+ .one('hide', function () {
+ $this.focus()
+ })
+ })
+ })
+
+}(window.jQuery);/* ===========================================================
+ * bootstrap-tooltip.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#tooltips
+ * Inspired by the original jQuery.tipsy by Jason Frame
+ * ===========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* TOOLTIP PUBLIC CLASS DEFINITION
+ * =============================== */
+
+ var Tooltip = function (element, options) {
+ this.init('tooltip', element, options)
+ }
+
+ Tooltip.prototype = {
+
+ constructor: Tooltip
+
+ , init: function (type, element, options) {
+ var eventIn
+ , eventOut
+
+ this.type = type
+ this.$element = $(element)
+ this.options = this.getOptions(options)
+ this.enabled = true
+
+ if (this.options.trigger == 'click') {
+ this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
+ } else if (this.options.trigger != 'manual') {
+ eventIn = this.options.trigger == 'hover' ? 'mouseenter' : 'focus'
+ eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur'
+ this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
+ this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
+ }
+
+ this.options.selector ?
+ (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
+ this.fixTitle()
+ }
+
+ , getOptions: function (options) {
+ options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data())
+
+ if (options.delay && typeof options.delay == 'number') {
+ options.delay = {
+ show: options.delay
+ , hide: options.delay
+ }
+ }
+
+ return options
+ }
+
+ , enter: function (e) {
+ var self = $(e.currentTarget)[this.type](this._options).data(this.type)
+
+ if (!self.options.delay || !self.options.delay.show) return self.show()
+
+ clearTimeout(this.timeout)
+ self.hoverState = 'in'
+ this.timeout = setTimeout(function() {
+ if (self.hoverState == 'in') self.show()
+ }, self.options.delay.show)
+ }
+
+ , leave: function (e) {
+ var self = $(e.currentTarget)[this.type](this._options).data(this.type)
+
+ if (this.timeout) clearTimeout(this.timeout)
+ if (!self.options.delay || !self.options.delay.hide) return self.hide()
+
+ self.hoverState = 'out'
+ this.timeout = setTimeout(function() {
+ if (self.hoverState == 'out') self.hide()
+ }, self.options.delay.hide)
+ }
+
+ , show: function () {
+ var $tip
+ , inside
+ , pos
+ , actualWidth
+ , actualHeight
+ , placement
+ , tp
+
+ if (this.hasContent() && this.enabled) {
+ $tip = this.tip()
+ this.setContent()
+
+ if (this.options.animation) {
+ $tip.addClass('fade')
+ }
+
+ placement = typeof this.options.placement == 'function' ?
+ this.options.placement.call(this, $tip[0], this.$element[0]) :
+ this.options.placement
+
+ inside = /in/.test(placement)
+
+ $tip
+ .remove()
+ .css({ top: 0, left: 0, display: 'block' })
+ .appendTo(inside ? this.$element : document.body)
+
+ pos = this.getPosition(inside)
+
+ actualWidth = $tip[0].offsetWidth
+ actualHeight = $tip[0].offsetHeight
+
+ switch (inside ? placement.split(' ')[1] : placement) {
+ case 'bottom':
+ tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
+ break
+ case 'top':
+ tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
+ break
+ case 'left':
+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
+ break
+ case 'right':
+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
+ break
+ }
+
+ $tip
+ .css(tp)
+ .addClass(placement)
+ .addClass('in')
+ }
+ }
+
+ , setContent: function () {
+ var $tip = this.tip()
+ , title = this.getTitle()
+
+ $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
+ $tip.removeClass('fade in top bottom left right')
+ }
+
+ , hide: function () {
+ var that = this
+ , $tip = this.tip()
+
+ $tip.removeClass('in')
+
+ function removeWithAnimation() {
+ var timeout = setTimeout(function () {
+ $tip.off($.support.transition.end).remove()
+ }, 500)
+
+ $tip.one($.support.transition.end, function () {
+ clearTimeout(timeout)
+ $tip.remove()
+ })
+ }
+
+ $.support.transition && this.$tip.hasClass('fade') ?
+ removeWithAnimation() :
+ $tip.remove()
+
+ return this
+ }
+
+ , fixTitle: function () {
+ var $e = this.$element
+ if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
+ $e.attr('data-original-title', $e.attr('title') || '').removeAttr('title')
+ }
+ }
+
+ , hasContent: function () {
+ return this.getTitle()
+ }
+
+ , getPosition: function (inside) {
+ return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), {
+ width: this.$element[0].offsetWidth
+ , height: this.$element[0].offsetHeight
+ })
+ }
+
+ , getTitle: function () {
+ var title
+ , $e = this.$element
+ , o = this.options
+
+ title = $e.attr('data-original-title')
+ || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
+
+ return title
+ }
+
+ , tip: function () {
+ return this.$tip = this.$tip || $(this.options.template)
+ }
+
+ , validate: function () {
+ if (!this.$element[0].parentNode) {
+ this.hide()
+ this.$element = null
+ this.options = null
+ }
+ }
+
+ , enable: function () {
+ this.enabled = true
+ }
+
+ , disable: function () {
+ this.enabled = false
+ }
+
+ , toggleEnabled: function () {
+ this.enabled = !this.enabled
+ }
+
+ , toggle: function () {
+ this[this.tip().hasClass('in') ? 'hide' : 'show']()
+ }
+
+ , destroy: function () {
+ this.hide().$element.off('.' + this.type).removeData(this.type)
+ }
+
+ }
+
+
+ /* TOOLTIP PLUGIN DEFINITION
+ * ========================= */
+
+ $.fn.tooltip = function ( option ) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('tooltip')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.tooltip.Constructor = Tooltip
+
+ $.fn.tooltip.defaults = {
+ animation: true
+ , placement: 'top'
+ , selector: false
+ , template: ''
+ , trigger: 'hover'
+ , title: ''
+ , delay: 0
+ , html: true
+ }
+
+}(window.jQuery);
+/* ===========================================================
+ * bootstrap-popover.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#popovers
+ * ===========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * =========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* POPOVER PUBLIC CLASS DEFINITION
+ * =============================== */
+
+ var Popover = function (element, options) {
+ this.init('popover', element, options)
+ }
+
+
+ /* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js
+ ========================================== */
+
+ Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, {
+
+ constructor: Popover
+
+ , setContent: function () {
+ var $tip = this.tip()
+ , title = this.getTitle()
+ , content = this.getContent()
+
+ $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
+ $tip.find('.popover-content > *')[this.options.html ? 'html' : 'text'](content)
+
+ $tip.removeClass('fade top bottom left right in')
+ }
+
+ , hasContent: function () {
+ return this.getTitle() || this.getContent()
+ }
+
+ , getContent: function () {
+ var content
+ , $e = this.$element
+ , o = this.options
+
+ content = $e.attr('data-content')
+ || (typeof o.content == 'function' ? o.content.call($e[0]) : o.content)
+
+ return content
+ }
+
+ , tip: function () {
+ if (!this.$tip) {
+ this.$tip = $(this.options.template)
+ }
+ return this.$tip
+ }
+
+ , destroy: function () {
+ this.hide().$element.off('.' + this.type).removeData(this.type)
+ }
+
+ })
+
+
+ /* POPOVER PLUGIN DEFINITION
+ * ======================= */
+
+ $.fn.popover = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('popover')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('popover', (data = new Popover(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.popover.Constructor = Popover
+
+ $.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, {
+ placement: 'right'
+ , trigger: 'click'
+ , content: ''
+ , template: ''
+ })
+
+}(window.jQuery);/* =============================================================
+ * bootstrap-scrollspy.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#scrollspy
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* SCROLLSPY CLASS DEFINITION
+ * ========================== */
+
+ function ScrollSpy(element, options) {
+ var process = $.proxy(this.process, this)
+ , $element = $(element).is('body') ? $(window) : $(element)
+ , href
+ this.options = $.extend({}, $.fn.scrollspy.defaults, options)
+ this.$scrollElement = $element.on('scroll.scroll-spy.data-api', process)
+ this.selector = (this.options.target
+ || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+ || '') + ' .nav li > a'
+ this.$body = $('body')
+ this.refresh()
+ this.process()
+ }
+
+ ScrollSpy.prototype = {
+
+ constructor: ScrollSpy
+
+ , refresh: function () {
+ var self = this
+ , $targets
+
+ this.offsets = $([])
+ this.targets = $([])
+
+ $targets = this.$body
+ .find(this.selector)
+ .map(function () {
+ var $el = $(this)
+ , href = $el.data('target') || $el.attr('href')
+ , $href = /^#\w/.test(href) && $(href)
+ return ( $href
+ && $href.length
+ && [[ $href.position().top, href ]] ) || null
+ })
+ .sort(function (a, b) { return a[0] - b[0] })
+ .each(function () {
+ self.offsets.push(this[0])
+ self.targets.push(this[1])
+ })
+ }
+
+ , process: function () {
+ var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
+ , scrollHeight = this.$scrollElement[0].scrollHeight || this.$body[0].scrollHeight
+ , maxScroll = scrollHeight - this.$scrollElement.height()
+ , offsets = this.offsets
+ , targets = this.targets
+ , activeTarget = this.activeTarget
+ , i
+
+ if (scrollTop >= maxScroll) {
+ return activeTarget != (i = targets.last()[0])
+ && this.activate ( i )
+ }
+
+ for (i = offsets.length; i--;) {
+ activeTarget != targets[i]
+ && scrollTop >= offsets[i]
+ && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
+ && this.activate( targets[i] )
+ }
+ }
+
+ , activate: function (target) {
+ var active
+ , selector
+
+ this.activeTarget = target
+
+ $(this.selector)
+ .parent('.active')
+ .removeClass('active')
+
+ selector = this.selector
+ + '[data-target="' + target + '"],'
+ + this.selector + '[href="' + target + '"]'
+
+ active = $(selector)
+ .parent('li')
+ .addClass('active')
+
+ if (active.parent('.dropdown-menu').length) {
+ active = active.closest('li.dropdown').addClass('active')
+ }
+
+ active.trigger('activate')
+ }
+
+ }
+
+
+ /* SCROLLSPY PLUGIN DEFINITION
+ * =========================== */
+
+ $.fn.scrollspy = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('scrollspy')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('scrollspy', (data = new ScrollSpy(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.scrollspy.Constructor = ScrollSpy
+
+ $.fn.scrollspy.defaults = {
+ offset: 10
+ }
+
+
+ /* SCROLLSPY DATA-API
+ * ================== */
+
+ $(window).on('load', function () {
+ $('[data-spy="scroll"]').each(function () {
+ var $spy = $(this)
+ $spy.scrollspy($spy.data())
+ })
+ })
+
+}(window.jQuery);/* ========================================================
+ * bootstrap-tab.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#tabs
+ * ========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ======================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* TAB CLASS DEFINITION
+ * ==================== */
+
+ var Tab = function (element) {
+ this.element = $(element)
+ }
+
+ Tab.prototype = {
+
+ constructor: Tab
+
+ , show: function () {
+ var $this = this.element
+ , $ul = $this.closest('ul:not(.dropdown-menu)')
+ , selector = $this.attr('data-target')
+ , previous
+ , $target
+ , e
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+ }
+
+ if ( $this.parent('li').hasClass('active') ) return
+
+ previous = $ul.find('.active a').last()[0]
+
+ e = $.Event('show', {
+ relatedTarget: previous
+ })
+
+ $this.trigger(e)
+
+ if (e.isDefaultPrevented()) return
+
+ $target = $(selector)
+
+ this.activate($this.parent('li'), $ul)
+ this.activate($target, $target.parent(), function () {
+ $this.trigger({
+ type: 'shown'
+ , relatedTarget: previous
+ })
+ })
+ }
+
+ , activate: function ( element, container, callback) {
+ var $active = container.find('> .active')
+ , transition = callback
+ && $.support.transition
+ && $active.hasClass('fade')
+
+ function next() {
+ $active
+ .removeClass('active')
+ .find('> .dropdown-menu > .active')
+ .removeClass('active')
+
+ element.addClass('active')
+
+ if (transition) {
+ element[0].offsetWidth // reflow for transition
+ element.addClass('in')
+ } else {
+ element.removeClass('fade')
+ }
+
+ if ( element.parent('.dropdown-menu') ) {
+ element.closest('li.dropdown').addClass('active')
+ }
+
+ callback && callback()
+ }
+
+ transition ?
+ $active.one($.support.transition.end, next) :
+ next()
+
+ $active.removeClass('in')
+ }
+ }
+
+
+ /* TAB PLUGIN DEFINITION
+ * ===================== */
+
+ $.fn.tab = function ( option ) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('tab')
+ if (!data) $this.data('tab', (data = new Tab(this)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.tab.Constructor = Tab
+
+
+ /* TAB DATA-API
+ * ============ */
+
+ $(function () {
+ $('body').on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
+ e.preventDefault()
+ $(this).tab('show')
+ })
+ })
+
+}(window.jQuery);/* =============================================================
+ * bootstrap-typeahead.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#typeahead
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function($){
+
+ "use strict"; // jshint ;_;
+
+
+ /* TYPEAHEAD PUBLIC CLASS DEFINITION
+ * ================================= */
+
+ var Typeahead = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, $.fn.typeahead.defaults, options)
+ this.matcher = this.options.matcher || this.matcher
+ this.sorter = this.options.sorter || this.sorter
+ this.highlighter = this.options.highlighter || this.highlighter
+ this.updater = this.options.updater || this.updater
+ this.$menu = $(this.options.menu).appendTo('body')
+ this.source = this.options.source
+ this.shown = false
+ this.listen()
+ }
+
+ Typeahead.prototype = {
+
+ constructor: Typeahead
+
+ , select: function () {
+ var val = this.$menu.find('.active').attr('data-value')
+ this.$element
+ .val(this.updater(val))
+ .change()
+ return this.hide()
+ }
+
+ , updater: function (item) {
+ return item
+ }
+
+ , show: function () {
+ var pos = $.extend({}, this.$element.offset(), {
+ height: this.$element[0].offsetHeight
+ })
+
+ this.$menu.css({
+ top: pos.top + pos.height
+ , left: pos.left
+ })
+
+ this.$menu.show()
+ this.shown = true
+ return this
+ }
+
+ , hide: function () {
+ this.$menu.hide()
+ this.shown = false
+ return this
+ }
+
+ , lookup: function (event) {
+ var items
+
+ this.query = this.$element.val()
+
+ if (!this.query || this.query.length < this.options.minLength) {
+ return this.shown ? this.hide() : this
+ }
+
+ items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source
+
+ return items ? this.process(items) : this
+ }
+
+ , process: function (items) {
+ var that = this
+
+ items = $.grep(items, function (item) {
+ return that.matcher(item)
+ })
+
+ items = this.sorter(items)
+
+ if (!items.length) {
+ return this.shown ? this.hide() : this
+ }
+
+ return this.render(items.slice(0, this.options.items)).show()
+ }
+
+ , matcher: function (item) {
+ return ~item.toLowerCase().indexOf(this.query.toLowerCase())
+ }
+
+ , sorter: function (items) {
+ var beginswith = []
+ , caseSensitive = []
+ , caseInsensitive = []
+ , item
+
+ while (item = items.shift()) {
+ if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
+ else if (~item.indexOf(this.query)) caseSensitive.push(item)
+ else caseInsensitive.push(item)
+ }
+
+ return beginswith.concat(caseSensitive, caseInsensitive)
+ }
+
+ , highlighter: function (item) {
+ var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
+ return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
+ return '' + match + ' '
+ })
+ }
+
+ , render: function (items) {
+ var that = this
+
+ items = $(items).map(function (i, item) {
+ i = $(that.options.item).attr('data-value', item)
+ i.find('a').html(that.highlighter(item))
+ return i[0]
+ })
+
+ items.first().addClass('active')
+ this.$menu.html(items)
+ return this
+ }
+
+ , next: function (event) {
+ var active = this.$menu.find('.active').removeClass('active')
+ , next = active.next()
+
+ if (!next.length) {
+ next = $(this.$menu.find('li')[0])
+ }
+
+ next.addClass('active')
+ }
+
+ , prev: function (event) {
+ var active = this.$menu.find('.active').removeClass('active')
+ , prev = active.prev()
+
+ if (!prev.length) {
+ prev = this.$menu.find('li').last()
+ }
+
+ prev.addClass('active')
+ }
+
+ , listen: function () {
+ this.$element
+ .on('blur', $.proxy(this.blur, this))
+ .on('keypress', $.proxy(this.keypress, this))
+ .on('keyup', $.proxy(this.keyup, this))
+
+ if ($.browser.chrome || $.browser.webkit || $.browser.msie) {
+ this.$element.on('keydown', $.proxy(this.keydown, this))
+ }
+
+ this.$menu
+ .on('click', $.proxy(this.click, this))
+ .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
+ }
+
+ , move: function (e) {
+ if (!this.shown) return
+
+ switch(e.keyCode) {
+ case 9: // tab
+ case 13: // enter
+ case 27: // escape
+ e.preventDefault()
+ break
+
+ case 38: // up arrow
+ e.preventDefault()
+ this.prev()
+ break
+
+ case 40: // down arrow
+ e.preventDefault()
+ this.next()
+ break
+ }
+
+ e.stopPropagation()
+ }
+
+ , keydown: function (e) {
+ this.suppressKeyPressRepeat = !~$.inArray(e.keyCode, [40,38,9,13,27])
+ this.move(e)
+ }
+
+ , keypress: function (e) {
+ if (this.suppressKeyPressRepeat) return
+ this.move(e)
+ }
+
+ , keyup: function (e) {
+ switch(e.keyCode) {
+ case 40: // down arrow
+ case 38: // up arrow
+ break
+
+ case 9: // tab
+ case 13: // enter
+ if (!this.shown) return
+ this.select()
+ break
+
+ case 27: // escape
+ if (!this.shown) return
+ this.hide()
+ break
+
+ default:
+ this.lookup()
+ }
+
+ e.stopPropagation()
+ e.preventDefault()
+ }
+
+ , blur: function (e) {
+ var that = this
+ setTimeout(function () { that.hide() }, 150)
+ }
+
+ , click: function (e) {
+ e.stopPropagation()
+ e.preventDefault()
+ this.select()
+ }
+
+ , mouseenter: function (e) {
+ this.$menu.find('.active').removeClass('active')
+ $(e.currentTarget).addClass('active')
+ }
+
+ }
+
+
+ /* TYPEAHEAD PLUGIN DEFINITION
+ * =========================== */
+
+ $.fn.typeahead = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('typeahead')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.typeahead.defaults = {
+ source: []
+ , items: 8
+ , menu: ''
+ , item: ' '
+ , minLength: 1
+ }
+
+ $.fn.typeahead.Constructor = Typeahead
+
+
+ /* TYPEAHEAD DATA-API
+ * ================== */
+
+ $(function () {
+ $('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
+ var $this = $(this)
+ if ($this.data('typeahead')) return
+ e.preventDefault()
+ $this.typeahead($this.data())
+ })
+ })
+
+}(window.jQuery);
+/* ==========================================================
+ * bootstrap-affix.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#affix
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* AFFIX CLASS DEFINITION
+ * ====================== */
+
+ var Affix = function (element, options) {
+ this.options = $.extend({}, $.fn.affix.defaults, options)
+ this.$window = $(window).on('scroll.affix.data-api', $.proxy(this.checkPosition, this))
+ this.$element = $(element)
+ this.checkPosition()
+ }
+
+ Affix.prototype.checkPosition = function () {
+ if (!this.$element.is(':visible')) return
+
+ var scrollHeight = $(document).height()
+ , scrollTop = this.$window.scrollTop()
+ , position = this.$element.offset()
+ , offset = this.options.offset
+ , offsetBottom = offset.bottom
+ , offsetTop = offset.top
+ , reset = 'affix affix-top affix-bottom'
+ , affix
+
+ if (typeof offset != 'object') offsetBottom = offsetTop = offset
+ if (typeof offsetTop == 'function') offsetTop = offset.top()
+ if (typeof offsetBottom == 'function') offsetBottom = offset.bottom()
+
+ affix = this.unpin != null && (scrollTop + this.unpin <= position.top) ?
+ false : offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ?
+ 'bottom' : offsetTop != null && scrollTop <= offsetTop ?
+ 'top' : false
+
+ if (this.affixed === affix) return
+
+ this.affixed = affix
+ this.unpin = affix == 'bottom' ? position.top - scrollTop : null
+
+ this.$element.removeClass(reset).addClass('affix' + (affix ? '-' + affix : ''))
+ }
+
+
+ /* AFFIX PLUGIN DEFINITION
+ * ======================= */
+
+ $.fn.affix = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('affix')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('affix', (data = new Affix(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.affix.Constructor = Affix
+
+ $.fn.affix.defaults = {
+ offset: 0
+ }
+
+
+ /* AFFIX DATA-API
+ * ============== */
+
+ $(window).on('load', function () {
+ $('[data-spy="affix"]').each(function () {
+ var $spy = $(this)
+ , data = $spy.data()
+
+ data.offset = data.offset || {}
+
+ data.offsetBottom && (data.offset.bottom = data.offsetBottom)
+ data.offsetTop && (data.offset.top = data.offsetTop)
+
+ $spy.affix(data)
+ })
+ })
+
+
+}(window.jQuery);
\ No newline at end of file
diff --git a/bootstrap/tmp/bootstrap.min.js b/bootstrap/tmp/bootstrap.min.js
new file mode 100644
index 0000000..06a7765
--- /dev/null
+++ b/bootstrap/tmp/bootstrap.min.js
@@ -0,0 +1 @@
+!function(e){e(function(){"use strict";e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()},e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e(function(){e("body").on("click.alert.data-api",t,n.prototype.close)})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")},e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e(function(){e("body").on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=n,this.options.slide&&this.slide(this.options.slide),this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},to:function(t){var n=this.$element.find(".item.active"),r=n.parent().children(),i=r.index(n),s=this;if(t>r.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){s.to(t)}):i==t?this.pause().cycle():this.slide(t>i?"next":"prev",e(r[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle()),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f=e.Event("slide",{relatedTarget:i[0]});this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u]();if(i.hasClass("active"))return;if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}},e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e(function(){e("body").on("click.carousel.data-api","[data-slide]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=!i.data("modal")&&e.extend({},i.data(),n.data());i.carousel(s),t.preventDefault()})})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning)return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning)return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}},e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=typeof n=="object"&&n;i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e(function(){e("body").on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})})}(window.jQuery),!function(e){"use strict";function r(){i(e(t)).removeClass("open")}function i(t){var n=t.attr("data-target"),r;return n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=e(n),r.length||(r=t.parent()),r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||(s.toggleClass("open"),n.focus()),!1},keydown:function(t){var n,r,s,o,u,a;if(!/(38|40|27)/.test(t.keyCode))return;n=e(this),t.preventDefault(),t.stopPropagation();if(n.is(".disabled, :disabled"))return;o=i(n),u=o.hasClass("open");if(!u||u&&t.keyCode==27)return n.click();r=e("[role=menu] li:not(.divider) a",o);if(!r.length)return;a=r.index(r.filter(":focus")),t.keyCode==38&&a>0&&a--,t.keyCode==40&&a ').appendTo(document.body),this.options.backdrop!="static"&&this.$backdrop.click(e.proxy(this.hide,this)),i&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),i?this.$backdrop.one(e.support.transition.end,t):t()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),e.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(e.support.transition.end,e.proxy(this.removeBackdrop,this)):this.removeBackdrop()):t&&t()}},e.fn.modal=function(n){return this.each(function(){var r=e(this),i=r.data("modal"),s=e.extend({},e.fn.modal.defaults,r.data(),typeof n=="object"&&n);i||r.data("modal",i=new t(this,s)),typeof n=="string"?i[n]():s.show&&i.show()})},e.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},e.fn.modal.Constructor=t,e(function(){e("body").on("click.modal.data-api",'[data-toggle="modal"]',function(t){var n=e(this),r=n.attr("href"),i=e(n.attr("data-target")||r&&r.replace(/.*(?=#[^\s]+$)/,"")),s=i.data("modal")?"toggle":e.extend({remote:!/#/.test(r)&&r},i.data(),n.data());t.preventDefault(),i.modal(s).one("hide",function(){n.focus()})})})}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("tooltip",e,t)};t.prototype={constructor:t,init:function(t,n,r){var i,s;this.type=t,this.$element=e(n),this.options=this.getOptions(r),this.enabled=!0,this.options.trigger=="click"?this.$element.on("click."+this.type,this.options.selector,e.proxy(this.toggle,this)):this.options.trigger!="manual"&&(i=this.options.trigger=="hover"?"mouseenter":"focus",s=this.options.trigger=="hover"?"mouseleave":"blur",this.$element.on(i+"."+this.type,this.options.selector,e.proxy(this.enter,this)),this.$element.on(s+"."+this.type,this.options.selector,e.proxy(this.leave,this))),this.options.selector?this._options=e.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(t){return t=e.extend({},e.fn[this.type].defaults,t,this.$element.data()),t.delay&&typeof t.delay=="number"&&(t.delay={show:t.delay,hide:t.delay}),t},enter:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);if(!n.options.delay||!n.options.delay.show)return n.show();clearTimeout(this.timeout),n.hoverState="in",this.timeout=setTimeout(function(){n.hoverState=="in"&&n.show()},n.options.delay.show)},leave:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!n.options.delay||!n.options.delay.hide)return n.hide();n.hoverState="out",this.timeout=setTimeout(function(){n.hoverState=="out"&&n.hide()},n.options.delay.hide)},show:function(){var e,t,n,r,i,s,o;if(this.hasContent()&&this.enabled){e=this.tip(),this.setContent(),this.options.animation&&e.addClass("fade"),s=typeof this.options.placement=="function"?this.options.placement.call(this,e[0],this.$element[0]):this.options.placement,t=/in/.test(s),e.remove().css({top:0,left:0,display:"block"}).appendTo(t?this.$element:document.body),n=this.getPosition(t),r=e[0].offsetWidth,i=e[0].offsetHeight;switch(t?s.split(" ")[1]:s){case"bottom":o={top:n.top+n.height,left:n.left+n.width/2-r/2};break;case"top":o={top:n.top-i,left:n.left+n.width/2-r/2};break;case"left":o={top:n.top+n.height/2-i/2,left:n.left-r};break;case"right":o={top:n.top+n.height/2-i/2,left:n.left+n.width}}e.css(o).addClass(s).addClass("in")}},setContent:function(){var e=this.tip(),t=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](t),e.removeClass("fade in top bottom left right")},hide:function(){function r(){var t=setTimeout(function(){n.off(e.support.transition.end).remove()},500);n.one(e.support.transition.end,function(){clearTimeout(t),n.remove()})}var t=this,n=this.tip();return n.removeClass("in"),e.support.transition&&this.$tip.hasClass("fade")?r():n.remove(),this},fixTitle:function(){var e=this.$element;(e.attr("title")||typeof e.attr("data-original-title")!="string")&&e.attr("data-original-title",e.attr("title")||"").removeAttr("title")},hasContent:function(){return this.getTitle()},getPosition:function(t){return e.extend({},t?{top:0,left:0}:this.$element.offset(),{width:this.$element[0].offsetWidth,height:this.$element[0].offsetHeight})},getTitle:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-original-title")||(typeof n.title=="function"?n.title.call(t[0]):n.title),e},tip:function(){return this.$tip=this.$tip||e(this.options.template)},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(){this[this.tip().hasClass("in")?"hide":"show"]()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}},e.fn.tooltip=function(n){return this.each(function(){var r=e(this),i=r.data("tooltip"),s=typeof n=="object"&&n;i||r.data("tooltip",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.tooltip.Constructor=t,e.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'',trigger:"hover",title:"",delay:0,html:!0}}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("popover",e,t)};t.prototype=e.extend({},e.fn.tooltip.Constructor.prototype,{constructor:t,setContent:function(){var e=this.tip(),t=this.getTitle(),n=this.getContent();e.find(".popover-title")[this.options.html?"html":"text"](t),e.find(".popover-content > *")[this.options.html?"html":"text"](n),e.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-content")||(typeof n.content=="function"?n.content.call(t[0]):n.content),e},tip:function(){return this.$tip||(this.$tip=e(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}}),e.fn.popover=function(n){return this.each(function(){var r=e(this),i=r.data("popover"),s=typeof n=="object"&&n;i||r.data("popover",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.popover.Constructor=t,e.fn.popover.defaults=e.extend({},e.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:''})}(window.jQuery),!function(e){"use strict";function t(t,n){var r=e.proxy(this.process,this),i=e(t).is("body")?e(window):e(t),s;this.options=e.extend({},e.fn.scrollspy.defaults,n),this.$scrollElement=i.on("scroll.scroll-spy.data-api",r),this.selector=(this.options.target||(s=e(t).attr("href"))&&s.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=e("body"),this.refresh(),this.process()}t.prototype={constructor:t,refresh:function(){var t=this,n;this.offsets=e([]),this.targets=e([]),n=this.$body.find(this.selector).map(function(){var t=e(this),n=t.data("target")||t.attr("href"),r=/^#\w/.test(n)&&e(n);return r&&r.length&&[[r.position().top,n]]||null}).sort(function(e,t){return e[0]-t[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},process:function(){var e=this.$scrollElement.scrollTop()+this.options.offset,t=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,n=t-this.$scrollElement.height(),r=this.offsets,i=this.targets,s=this.activeTarget,o;if(e>=n)return s!=(o=i.last()[0])&&this.activate(o);for(o=r.length;o--;)s!=i[o]&&e>=r[o]&&(!r[o+1]||e<=r[o+1])&&this.activate(i[o])},activate:function(t){var n,r;this.activeTarget=t,e(this.selector).parent(".active").removeClass("active"),r=this.selector+'[data-target="'+t+'"],'+this.selector+'[href="'+t+'"]',n=e(r).parent("li").addClass("active"),n.parent(".dropdown-menu").length&&(n=n.closest("li.dropdown").addClass("active")),n.trigger("activate")}},e.fn.scrollspy=function(n){return this.each(function(){var r=e(this),i=r.data("scrollspy"),s=typeof n=="object"&&n;i||r.data("scrollspy",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.scrollspy.Constructor=t,e.fn.scrollspy.defaults={offset:10},e(window).on("load",function(){e('[data-spy="scroll"]').each(function(){var t=e(this);t.scrollspy(t.data())})})}(window.jQuery),!function(e){"use strict";var t=function(t){this.element=e(t)};t.prototype={constructor:t,show:function(){var t=this.element,n=t.closest("ul:not(.dropdown-menu)"),r=t.attr("data-target"),i,s,o;r||(r=t.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,""));if(t.parent("li").hasClass("active"))return;i=n.find(".active a").last()[0],o=e.Event("show",{relatedTarget:i}),t.trigger(o);if(o.isDefaultPrevented())return;s=e(r),this.activate(t.parent("li"),n),this.activate(s,s.parent(),function(){t.trigger({type:"shown",relatedTarget:i})})},activate:function(t,n,r){function o(){i.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),t.addClass("active"),s?(t[0].offsetWidth,t.addClass("in")):t.removeClass("fade"),t.parent(".dropdown-menu")&&t.closest("li.dropdown").addClass("active"),r&&r()}var i=n.find("> .active"),s=r&&e.support.transition&&i.hasClass("fade");s?i.one(e.support.transition.end,o):o(),i.removeClass("in")}},e.fn.tab=function(n){return this.each(function(){var r=e(this),i=r.data("tab");i||r.data("tab",i=new t(this)),typeof n=="string"&&i[n]()})},e.fn.tab.Constructor=t,e(function(){e("body").on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(t){t.preventDefault(),e(this).tab("show")})})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.typeahead.defaults,n),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.$menu=e(this.options.menu).appendTo("body"),this.source=this.options.source,this.shown=!1,this.listen()};t.prototype={constructor:t,select:function(){var e=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(e)).change(),this.hide()},updater:function(e){return e},show:function(){var t=e.extend({},this.$element.offset(),{height:this.$element[0].offsetHeight});return this.$menu.css({top:t.top+t.height,left:t.left}),this.$menu.show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(t){var n;return this.query=this.$element.val(),!this.query||this.query.length"+t+""})},render:function(t){var n=this;return t=e(t).map(function(t,r){return t=e(n.options.item).attr("data-value",r),t.find("a").html(n.highlighter(r)),t[0]}),t.first().addClass("active"),this.$menu.html(t),this},next:function(t){var n=this.$menu.find(".active").removeClass("active"),r=n.next();r.length||(r=e(this.$menu.find("li")[0])),r.addClass("active")},prev:function(e){var t=this.$menu.find(".active").removeClass("active"),n=t.prev();n.length||(n=this.$menu.find("li").last()),n.addClass("active")},listen:function(){this.$element.on("blur",e.proxy(this.blur,this)).on("keypress",e.proxy(this.keypress,this)).on("keyup",e.proxy(this.keyup,this)),(e.browser.chrome||e.browser.webkit||e.browser.msie)&&this.$element.on("keydown",e.proxy(this.keydown,this)),this.$menu.on("click",e.proxy(this.click,this)).on("mouseenter","li",e.proxy(this.mouseenter,this))},move:function(e){if(!this.shown)return;switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:e.preventDefault(),this.prev();break;case 40:e.preventDefault(),this.next()}e.stopPropagation()},keydown:function(t){this.suppressKeyPressRepeat=!~e.inArray(t.keyCode,[40,38,9,13,27]),this.move(t)},keypress:function(e){if(this.suppressKeyPressRepeat)return;this.move(e)},keyup:function(e){switch(e.keyCode){case 40:case 38:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}e.stopPropagation(),e.preventDefault()},blur:function(e){var t=this;setTimeout(function(){t.hide()},150)},click:function(e){e.stopPropagation(),e.preventDefault(),this.select()},mouseenter:function(t){this.$menu.find(".active").removeClass("active"),e(t.currentTarget).addClass("active")}},e.fn.typeahead=function(n){return this.each(function(){var r=e(this),i=r.data("typeahead"),s=typeof n=="object"&&n;i||r.data("typeahead",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.typeahead.defaults={source:[],items:8,menu:'',item:' ',minLength:1},e.fn.typeahead.Constructor=t,e(function(){e("body").on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(t){var n=e(this);if(n.data("typeahead"))return;t.preventDefault(),n.typeahead(n.data())})})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=e.extend({},e.fn.affix.defaults,n),this.$window=e(window).on("scroll.affix.data-api",e.proxy(this.checkPosition,this)),this.$element=e(t),this.checkPosition()};t.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var t=e(document).height(),n=this.$window.scrollTop(),r=this.$element.offset(),i=this.options.offset,s=i.bottom,o=i.top,u="affix affix-top affix-bottom",a;typeof i!="object"&&(s=o=i),typeof o=="function"&&(o=i.top()),typeof s=="function"&&(s=i.bottom()),a=this.unpin!=null&&n+this.unpin<=r.top?!1:s!=null&&r.top+this.$element.height()>=t-s?"bottom":o!=null&&n<=o?"top":!1;if(this.affixed===a)return;this.affixed=a,this.unpin=a=="bottom"?r.top-n:null,this.$element.removeClass(u).addClass("affix"+(a?"-"+a:""))},e.fn.affix=function(n){return this.each(function(){var r=e(this),i=r.data("affix"),s=typeof n=="object"&&n;i||r.data("affix",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.affix.Constructor=t,e.fn.affix.defaults={offset:0},e(window).on("load",function(){e('[data-spy="affix"]').each(function(){var t=e(this),n=t.data();n.offset=n.offset||{},n.offsetBottom&&(n.offset.bottom=n.offsetBottom),n.offsetTop&&(n.offset.top=n.offsetTop),t.affix(n)})})}(window.jQuery);
\ No newline at end of file
diff --git a/bootstrap/tmp/copyright.js b/bootstrap/tmp/copyright.js
new file mode 100644
index 0000000..d6d1e11
--- /dev/null
+++ b/bootstrap/tmp/copyright.js
@@ -0,0 +1 @@
+/*!\n* Bootstrap.js by @fat & @mdo\n* Copyright 2012 Twitter, Inc.\n* http://www.apache.org/licenses/LICENSE-2.0.txt\n*/
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..7fac8eb
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/com/biomed/Biomed.gwt.xml b/src/com/biomed/Biomed.gwt.xml
new file mode 100644
index 0000000..b7ebf1a
--- /dev/null
+++ b/src/com/biomed/Biomed.gwt.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/com/biomed/client/AbstractBiomedActivity.java b/src/com/biomed/client/AbstractBiomedActivity.java
new file mode 100644
index 0000000..6a1022f
--- /dev/null
+++ b/src/com/biomed/client/AbstractBiomedActivity.java
@@ -0,0 +1,26 @@
+package com.biomed.client;
+
+import com.biomed.client.place.BiomedPlace;
+
+public abstract class AbstractBiomedActivity implements BiomedActivity {
+
+ protected BiomedPlace place;
+
+ @Override
+ public String mayStop() {
+ return null;
+ }
+
+ @Override
+ public void onCancel() {
+ }
+
+ @Override
+ public void onStop() {
+ }
+
+ @Override
+ public void setPlace(BiomedPlace place) {
+ this.place = place;
+ }
+}
diff --git a/src/com/biomed/client/BiomedActivity.java b/src/com/biomed/client/BiomedActivity.java
new file mode 100644
index 0000000..d82623e
--- /dev/null
+++ b/src/com/biomed/client/BiomedActivity.java
@@ -0,0 +1,46 @@
+package com.biomed.client;
+
+import com.biomed.client.BiomedActivityManager.SiteContainer;
+import com.biomed.client.place.BiomedPlace;
+import com.google.gwt.user.client.ui.AcceptsOneWidget;
+import com.google.web.bindery.event.shared.EventBus;
+
+public interface BiomedActivity {
+ /**
+ * Called when the user is trying to navigate away from this activity.
+ *
+ * @return A message to display to the user, e.g. to warn of unsaved work, or
+ * null to say nothing
+ */
+ String mayStop();
+
+ /**
+ * Called when {@link #start} has not yet replied to its callback, but the
+ * user has lost interest.
+ */
+ void onCancel();
+
+ /**
+ * Called when the Activity's widget has been removed from view. All event
+ * handlers it registered will have been removed before this method is called.
+ */
+ void onStop();
+
+ /**
+ * Called when the Activity should ready its widget for the user. When the
+ * widget is ready (typically after an RPC response has been received),
+ * receiver should present it by calling
+ * {@link AcceptsOneWidget#setWidget} on the given panel.
+ *
+ * Any handlers attached to the provided event bus will be de-registered when
+ * the activity is stopped, so activities will rarely need to hold on to the
+ * {@link com.google.gwt.event.shared.HandlerRegistration HandlerRegistration}
+ * instances returned by {@link EventBus#addHandler}.
+ *
+ * @param panel the panel to display this activity's widget when it is ready
+ * @param eventBus the event bus
+ */
+ void start(SiteContainer panel, EventBus eventBus);
+
+ void setPlace(BiomedPlace place);
+}
diff --git a/src/com/biomed/client/BiomedActivityManager.java b/src/com/biomed/client/BiomedActivityManager.java
new file mode 100644
index 0000000..ba05281
--- /dev/null
+++ b/src/com/biomed/client/BiomedActivityManager.java
@@ -0,0 +1,266 @@
+package com.biomed.client;
+
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.biomed.client.place.BiomedPlace;
+import com.google.common.collect.Maps;
+import com.google.gwt.event.shared.UmbrellaException;
+import com.google.gwt.place.shared.PlaceChangeEvent;
+import com.google.gwt.place.shared.PlaceChangeRequestEvent;
+import com.google.gwt.user.client.ui.AcceptsOneWidget;
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.event.shared.HandlerRegistration;
+import com.google.web.bindery.event.shared.ResettableEventBus;
+
+public class BiomedActivityManager implements PlaceChangeEvent.Handler,
+ PlaceChangeRequestEvent.Handler {
+ /**
+ * Wraps our real display to prevent an Activity from taking it over if it is
+ * not the currentActivity.
+ */
+ public class SiteContainer {
+ private final BiomedActivity activity;
+
+ SiteContainer(BiomedActivity activity) {
+ this.activity = activity;
+ }
+
+ public void setWidget(BiomedSite site, IsWidget view) {
+ if (this.activity == BiomedActivityManager.this.currentActivity) {
+ startingNext = false;
+ showWidget(site, view);
+ }
+ }
+ }
+
+ private static final BiomedActivity NULL_ACTIVITY = new AbstractBiomedActivity() {
+ public void start(SiteContainer container, EventBus eventBus) {
+ }
+ };
+
+ private final BiomedActivityMapper mapper;
+
+ private final EventBus eventBus;
+
+ /*
+ * Note that we use the legacy class from com.google.gwt.event.shared, because
+ * we can't change the Activity interface.
+ */
+ private final ResettableEventBus stopperedEventBus;
+
+ private BiomedActivity currentActivity = NULL_ACTIVITY;
+
+ private Map sites = Maps.newHashMap();
+
+ private boolean startingNext = false;
+
+ private HandlerRegistration handlerRegistration;
+
+ /**
+ * Create an ActivityManager. Next call {@link #setDisplay}.
+ *
+ * @param mapper
+ * finds the {@link BiomedActivity} for a given
+ * {@link com.google.gwt.place.shared.Place}
+ * @param eventBus
+ * source of {@link PlaceChangeEvent} and
+ * {@link PlaceChangeRequestEvent} events.
+ */
+ public BiomedActivityManager(BiomedActivityMapper mapper, EventBus eventBus) {
+ this.mapper = mapper;
+ this.eventBus = eventBus;
+ this.stopperedEventBus = new ResettableEventBus(eventBus);
+ }
+
+ /**
+ * Returns an event bus which is in use by the currently running activity.
+ *
+ * Any handlers attached to the returned event bus will be de-registered when
+ * the current activity is stopped.
+ *
+ * @return the event bus used by the current activity
+ */
+ public EventBus getActiveEventBus() {
+ return stopperedEventBus;
+ }
+
+ /**
+ * Deactivate the current activity, find the next one from our ActivityMapper,
+ * and start it.
+ *
+ * The current activity's widget will be hidden immediately, which can cause
+ * flicker if the next activity provides its widget asynchronously. That can
+ * be minimized by decent caching. Perenially slow activities might mitigate
+ * this by providing a widget immediately, with some kind of "loading"
+ * treatment.
+ */
+ public void onPlaceChange(PlaceChangeEvent event) {
+ BiomedActivity nextActivity = getNextActivity(event);
+
+ Throwable caughtOnStop = null;
+ Throwable caughtOnCancel = null;
+ Throwable caughtOnStart = null;
+
+ if (nextActivity == null) {
+ nextActivity = NULL_ACTIVITY;
+ }
+
+ if (currentActivity.equals(nextActivity)) {
+ return;
+ }
+
+ if (startingNext) {
+ // The place changed again before the new current activity showed its
+ // widget
+ caughtOnCancel = tryStopOrCancel(false);
+ currentActivity = NULL_ACTIVITY;
+ startingNext = false;
+ } else if (!currentActivity.equals(NULL_ACTIVITY)) {
+ for (AcceptsOneWidget display : sites.values()) {
+ display.setWidget(null);
+ }
+
+ /*
+ * Kill off the activity's handlers, so it doesn't have to worry about
+ * them accidentally firing as a side effect of its tear down
+ */
+ stopperedEventBus.removeHandlers();
+ caughtOnStop = tryStopOrCancel(true);
+ }
+
+ currentActivity = nextActivity;
+
+ if (currentActivity.equals(NULL_ACTIVITY)) {
+ for (AcceptsOneWidget display : sites.values()) {
+ display.setWidget(null);
+ }
+ } else {
+ startingNext = true;
+ caughtOnStart = tryStart();
+ }
+
+ if (caughtOnStart != null || caughtOnCancel != null || caughtOnStop != null) {
+ Set causes = new LinkedHashSet();
+ if (caughtOnStop != null) {
+ causes.add(caughtOnStop);
+ }
+ if (caughtOnCancel != null) {
+ causes.add(caughtOnCancel);
+ }
+ if (caughtOnStart != null) {
+ causes.add(caughtOnStart);
+ }
+
+ throw new UmbrellaException(causes);
+ }
+ }
+
+ /**
+ * Reject the place change if the current activity is not willing to stop.
+ *
+ * @see com.google.gwt.place.shared.PlaceChangeRequestEvent.Handler#onPlaceChangeRequest(PlaceChangeRequestEvent)
+ */
+ public void onPlaceChangeRequest(PlaceChangeRequestEvent event) {
+ event.setWarning(currentActivity.mayStop());
+ }
+
+ /**
+ * Sets the display for the receiver, and has the side effect of starting or
+ * stopping its monitoring the event bus for place change events.
+ *
+ * If you are disposing of an ActivityManager, it is important to call
+ * setDisplay(null) to get it to deregister from the event bus, so that it can
+ * be garbage collected.
+ */
+ public void setSiteDisplay(BiomedSite site, AcceptsOneWidget display) {
+ boolean wasActive = sites.size() > 0;
+
+ if (display != null) {
+ sites.put(site, display);
+ } else {
+ sites.remove(site);
+ }
+
+ boolean willBeActive = sites.size() > 0;
+
+ if (wasActive != willBeActive) {
+ updateHandlers(willBeActive);
+ }
+ }
+
+ private BiomedActivity getNextActivity(PlaceChangeEvent event) {
+ if (sites.size() == 0) {
+ /*
+ * Display may have been nulled during PlaceChangeEvent dispatch. Don't
+ * bother the mapper, just return a null to ensure we shut down the
+ * current activity
+ */
+ return null;
+ }
+ return mapper.getActivity((BiomedPlace) event.getNewPlace());
+ }
+
+ private void showWidget(BiomedSite site, IsWidget view) {
+ if (sites.containsKey(site)) {
+ sites.get(site).setWidget(view);
+ }
+ }
+
+ private Throwable tryStart() {
+ Throwable caughtOnStart = null;
+ try {
+ /*
+ * Wrap the actual display with a per-call instance that protects the
+ * display from canceled or stopped activities, and which maintains our
+ * startingNext state.
+ */
+ currentActivity.start(new SiteContainer(currentActivity), stopperedEventBus);
+ } catch (Throwable t) {
+ caughtOnStart = t;
+ }
+ return caughtOnStart;
+ }
+
+ private Throwable tryStopOrCancel(boolean stop) {
+ Throwable caughtOnStop = null;
+ try {
+ if (stop) {
+ currentActivity.onStop();
+ } else {
+ currentActivity.onCancel();
+ }
+ } catch (Throwable t) {
+ caughtOnStop = t;
+ } finally {
+ /*
+ * Kill off the handlers again in case it was naughty and added new ones
+ * during onstop or oncancel
+ */
+ stopperedEventBus.removeHandlers();
+ }
+ return caughtOnStop;
+ }
+
+ private void updateHandlers(boolean activate) {
+ if (activate) {
+ final HandlerRegistration placeReg = eventBus.addHandler(PlaceChangeEvent.TYPE, this);
+ final HandlerRegistration placeRequestReg = eventBus.addHandler(PlaceChangeRequestEvent.TYPE,
+ this);
+
+ this.handlerRegistration = new HandlerRegistration() {
+ public void removeHandler() {
+ placeReg.removeHandler();
+ placeRequestReg.removeHandler();
+ }
+ };
+ } else {
+ if (handlerRegistration != null) {
+ handlerRegistration.removeHandler();
+ handlerRegistration = null;
+ }
+ }
+ }
+}
diff --git a/src/com/biomed/client/BiomedActivityMapper.java b/src/com/biomed/client/BiomedActivityMapper.java
new file mode 100644
index 0000000..d450fe6
--- /dev/null
+++ b/src/com/biomed/client/BiomedActivityMapper.java
@@ -0,0 +1,97 @@
+package com.biomed.client;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import com.biomed.client.activity.EditClientDevicesActivity;
+import com.biomed.client.activity.EditClientFrequencyActivity;
+import com.biomed.client.activity.EditClientOverviewActivity;
+import com.biomed.client.activity.EditClientWorkordersActivity;
+import com.biomed.client.activity.EditWorkorderActivity;
+import com.biomed.client.activity.NewClientActivity;
+import com.biomed.client.activity.NewMessageActivity;
+import com.biomed.client.activity.ViewClientsActivity;
+import com.biomed.client.activity.ViewScheduleActivity;
+import com.biomed.client.activity.ViewWorkordersActivity;
+import com.biomed.client.place.BiomedPlace;
+import com.biomed.client.place.EditClientDevicesPlace;
+import com.biomed.client.place.EditClientFrequencyPlace;
+import com.biomed.client.place.EditClientOverviewPlace;
+import com.biomed.client.place.EditClientWorkordersPlace;
+import com.biomed.client.place.EditWorkorderPlace;
+import com.biomed.client.place.NewClientPlace;
+import com.biomed.client.place.NewMessagePlace;
+import com.biomed.client.place.ViewClientsPlace;
+import com.biomed.client.place.ViewSchedulePlace;
+import com.biomed.client.place.ViewWorkordersPlace;
+import com.google.inject.Provider;
+
+@Singleton
+public class BiomedActivityMapper {
+
+ @Inject Provider editClientOverviewActivityProvider;
+ @Inject Provider editClientDevicesActivityProvider;
+ @Inject Provider editClientFrequencyActivityProvider;
+ @Inject Provider editClientWorkordersActivityProvider;
+ @Inject Provider editWorkorderActivityProvider;
+ @Inject Provider newClientActivityProvider;
+ @Inject Provider newMessageActivityProvider;
+ @Inject Provider viewClientsActivityProvider;
+ @Inject Provider viewScheduleActivityProvider;
+ @Inject Provider viewWorkordersActivityProvider;
+
+
+ public BiomedActivity getActivity(BiomedPlace place) {
+ BiomedActivity activity = getActivityInternal(place);
+ if (activity != null) {
+ activity.setPlace(place);
+ }
+
+ return activity;
+ }
+
+
+ private BiomedActivity getActivityInternal(BiomedPlace place) {
+ if (place instanceof EditClientOverviewPlace) {
+ return editClientOverviewActivityProvider.get();
+ }
+
+ if (place instanceof EditClientDevicesPlace) {
+ return editClientDevicesActivityProvider.get();
+ }
+
+ if (place instanceof EditClientFrequencyPlace) {
+ return editClientFrequencyActivityProvider.get();
+ }
+
+ if (place instanceof EditClientWorkordersPlace) {
+ return editClientWorkordersActivityProvider.get();
+ }
+
+ if (place instanceof EditWorkorderPlace) {
+ return editWorkorderActivityProvider.get();
+ }
+
+ if (place instanceof NewClientPlace) {
+ return newClientActivityProvider.get();
+ }
+
+ if (place instanceof NewMessagePlace) {
+ return newMessageActivityProvider.get();
+ }
+
+ if (place instanceof ViewClientsPlace) {
+ return viewClientsActivityProvider.get();
+ }
+
+ if (place instanceof ViewSchedulePlace) {
+ return viewScheduleActivityProvider.get();
+ }
+
+ if (place instanceof ViewWorkordersPlace) {
+ return viewWorkordersActivityProvider.get();
+ }
+
+ return null;
+ }
+}
diff --git a/src/com/biomed/client/BiomedEntryPoint.java b/src/com/biomed/client/BiomedEntryPoint.java
new file mode 100644
index 0000000..620900e
--- /dev/null
+++ b/src/com/biomed/client/BiomedEntryPoint.java
@@ -0,0 +1,18 @@
+package com.biomed.client;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.ui.RootPanel;
+
+public class BiomedEntryPoint implements EntryPoint {
+
+ private final BiomedGinjector injector = GWT.create(BiomedGinjector.class);
+
+ @Override
+ public void onModuleLoad() {
+ DOM.getElementById("loading").removeFromParent();
+ RootPanel.get().add(injector.getAppWidget());
+ injector.getPlaceHistoryHandler().handleCurrentHistory();
+ }
+}
diff --git a/src/com/biomed/client/BiomedGinModule.java b/src/com/biomed/client/BiomedGinModule.java
new file mode 100644
index 0000000..14d5d94
--- /dev/null
+++ b/src/com/biomed/client/BiomedGinModule.java
@@ -0,0 +1,44 @@
+package com.biomed.client;
+
+import javax.inject.Singleton;
+
+import com.biomed.client.dispatch.ClientDispatchGinModule;
+import com.biomed.client.place.ViewSchedulePlace;
+import com.biomed.client.services.ServicesGinModule;
+import com.google.gwt.inject.client.AbstractGinModule;
+import com.google.gwt.place.shared.PlaceController;
+import com.google.gwt.place.shared.PlaceHistoryHandler;
+import com.google.gwt.place.shared.PlaceHistoryMapper;
+import com.google.inject.Provides;
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.event.shared.SimpleEventBus;
+
+public class BiomedGinModule extends AbstractGinModule {
+
+ @Override
+ protected void configure() {
+ install(new ClientDispatchGinModule());
+ install(new ServicesGinModule());
+
+ bind(EventBus.class).to(SimpleEventBus.class).in(Singleton.class);
+ bind(PlaceHistoryMapper.class).to(BiomedPlaceHistoryMapper.class).in(Singleton.class);
+ }
+
+ @Provides
+ @Singleton
+ public PlaceHistoryHandler getHistoryHandler(PlaceController placeController,
+ PlaceHistoryMapper historyMapper, EventBus eventBus) {
+
+ PlaceHistoryHandler historyHandler = new PlaceHistoryHandler(historyMapper);
+ historyHandler.register(placeController, eventBus, ViewSchedulePlace.INSTANCE);
+
+ return historyHandler;
+ }
+
+ @Provides
+ @Singleton
+ public PlaceController providesPlaceController(EventBus eventBus) {
+ return new PlaceController(eventBus);
+ }
+
+}
diff --git a/src/com/biomed/client/BiomedGinjector.java b/src/com/biomed/client/BiomedGinjector.java
new file mode 100644
index 0000000..ea9c4dd
--- /dev/null
+++ b/src/com/biomed/client/BiomedGinjector.java
@@ -0,0 +1,14 @@
+package com.biomed.client;
+
+import com.biomed.client.ui.MainPanel;
+import com.google.gwt.inject.client.GinModules;
+import com.google.gwt.inject.client.Ginjector;
+import com.google.gwt.place.shared.PlaceHistoryHandler;
+
+@GinModules(BiomedGinModule.class)
+public interface BiomedGinjector extends Ginjector {
+
+ PlaceHistoryHandler getPlaceHistoryHandler();
+
+ MainPanel getAppWidget();
+}
diff --git a/src/com/biomed/client/BiomedPlaceHistoryMapper.java b/src/com/biomed/client/BiomedPlaceHistoryMapper.java
new file mode 100644
index 0000000..0dec046
--- /dev/null
+++ b/src/com/biomed/client/BiomedPlaceHistoryMapper.java
@@ -0,0 +1,29 @@
+package com.biomed.client;
+
+import com.biomed.client.place.EditClientDevicesPlace;
+import com.biomed.client.place.EditClientFrequencyPlace;
+import com.biomed.client.place.EditClientOverviewPlace;
+import com.biomed.client.place.EditClientWorkordersPlace;
+import com.biomed.client.place.EditWorkorderPlace;
+import com.biomed.client.place.NewClientPlace;
+import com.biomed.client.place.NewMessagePlace;
+import com.biomed.client.place.ViewClientsPlace;
+import com.biomed.client.place.ViewSchedulePlace;
+import com.biomed.client.place.ViewWorkordersPlace;
+import com.google.gwt.place.shared.PlaceHistoryMapper;
+import com.google.gwt.place.shared.WithTokenizers;
+
+
+@WithTokenizers({
+ EditClientDevicesPlace.Tokenizer.class,
+ EditClientFrequencyPlace.Tokenizer.class,
+ EditClientOverviewPlace.Tokenizer.class,
+ EditClientWorkordersPlace.Tokenizer.class,
+ EditWorkorderPlace.Tokenizer.class,
+ NewClientPlace.Tokenizer.class,
+ NewMessagePlace.Tokenizer.class,
+ ViewClientsPlace.Tokenizer.class,
+ ViewSchedulePlace.Tokenizer.class,
+ ViewWorkordersPlace.Tokenizer.class
+})
+public interface BiomedPlaceHistoryMapper extends PlaceHistoryMapper { }
diff --git a/src/com/biomed/client/BiomedSection.java b/src/com/biomed/client/BiomedSection.java
new file mode 100644
index 0000000..0ecfddc
--- /dev/null
+++ b/src/com/biomed/client/BiomedSection.java
@@ -0,0 +1,9 @@
+package com.biomed.client;
+
+public enum BiomedSection {
+ DASHBOARD,
+ CLIENTS,
+ MESSAGES,
+ WORKORDERS,
+ SCHEDULE
+}
diff --git a/src/com/biomed/client/BiomedSite.java b/src/com/biomed/client/BiomedSite.java
new file mode 100644
index 0000000..e1784cc
--- /dev/null
+++ b/src/com/biomed/client/BiomedSite.java
@@ -0,0 +1,6 @@
+package com.biomed.client;
+
+public enum BiomedSite {
+ CONTENT,
+ SIDEBAR
+}
diff --git a/src/com/biomed/client/activity/BaseClientActivity.java b/src/com/biomed/client/activity/BaseClientActivity.java
new file mode 100644
index 0000000..d84306c
--- /dev/null
+++ b/src/com/biomed/client/activity/BaseClientActivity.java
@@ -0,0 +1,116 @@
+package com.biomed.client.activity;
+
+import javax.inject.Inject;
+
+import com.biomed.client.AbstractBiomedActivity;
+import com.biomed.client.BiomedActivityManager.SiteContainer;
+import com.biomed.client.BiomedSite;
+import com.biomed.client.place.EditClientDevicesPlace;
+import com.biomed.client.place.EditClientFrequencyPlace;
+import com.biomed.client.place.EditClientOverviewPlace;
+import com.biomed.client.place.EditClientWorkordersPlace;
+import com.biomed.client.place.HasClientId;
+import com.biomed.client.ui.clienteditor.ClientEditorSidebarPanel;
+import com.biomed.client.ui.clienteditor.ClientEditorSidebarPanel.Tabs;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.place.shared.PlaceController;
+import com.google.inject.Provider;
+import com.google.web.bindery.event.shared.EventBus;
+
+@SuppressWarnings("rawtypes")
+public abstract class BaseClientActivity extends AbstractBiomedActivity implements
+ ValueChangeHandler {
+
+ @Inject
+ Provider sidebarProvider;
+
+ @Inject
+ PlaceController placeController;
+
+ private boolean isDirty;
+
+ protected abstract boolean showNavigationPanel();
+
+ protected Tabs getTab() {
+ return null;
+ }
+
+ protected abstract void createContentPanel(SiteContainer panel);
+
+ @Override
+ public final void start(SiteContainer panel, EventBus eventBus) {
+ if (showNavigationPanel()) {
+ ClientEditorSidebarPanel sidebar = sidebarProvider.get();
+ sidebar.setActive(getTab());
+ addSidebarEventHandlers(sidebar);
+ panel.setWidget(BiomedSite.SIDEBAR, sidebar);
+ }
+
+ createContentPanel(panel);
+ }
+
+ private void addSidebarEventHandlers(ClientEditorSidebarPanel sidebar) {
+ Tabs tab = getTab();
+
+ if (tab != Tabs.OVERVIEW) {
+ sidebar.getOverviewTab().addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ placeController.goTo(EditClientOverviewPlace.getPlace(getClientId()));
+ }
+ });
+ }
+
+ if (tab != Tabs.FREQUENCY) {
+ sidebar.getFrequencyTab().addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ placeController.goTo(EditClientFrequencyPlace.getPlace(getClientId()));
+ }
+ });
+ }
+
+ if (tab != Tabs.DEVICES) {
+ sidebar.getDevicesTab().addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ placeController.goTo(EditClientDevicesPlace.getPlace(getClientId()));
+ }
+ });
+ }
+
+ if (tab != Tabs.WORKORDERS) {
+ sidebar.getWorkordersTab().addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ placeController.goTo(EditClientWorkordersPlace.getPlace(getClientId()));
+ }
+ });
+ }
+ }
+
+ protected int getClientId() {
+ return ((HasClientId) place).getId();
+ }
+
+ protected void resetDirtyFlag() {
+ isDirty = false;
+ }
+
+ @Override
+ public String mayStop() {
+ if (isDirty) {
+ return "You have unsaved changed, are you sure?";
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void onValueChange(ValueChangeEvent event) {
+ isDirty = true;
+ }
+}
diff --git a/src/com/biomed/client/activity/BaseClientOverviewActivity.java b/src/com/biomed/client/activity/BaseClientOverviewActivity.java
new file mode 100644
index 0000000..3b1075a
--- /dev/null
+++ b/src/com/biomed/client/activity/BaseClientOverviewActivity.java
@@ -0,0 +1,102 @@
+package com.biomed.client.activity;
+
+import javax.inject.Inject;
+
+import com.biomed.client.dispatch.DefaultCallback;
+import com.biomed.client.services.BiomedService;
+import com.biomed.client.ui.clienteditor.ClientOverviewEditorPanel;
+import com.biomed.shared.api.EmptyResult;
+import com.biomed.shared.api.SaveClientAction;
+import com.biomed.shared.api.dto.ClientDTO;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+
+public abstract class BaseClientOverviewActivity extends BaseClientActivity {
+
+ @Inject
+ ClientOverviewEditorPanel view;
+
+ @Inject
+ BiomedService biomedService;
+
+ ClientDTO client;
+
+ @SuppressWarnings("unchecked")
+ protected void setupEvents() {
+ view.getIdentifierField().addValueChangeHandler(this);
+ view.getNameField().addValueChangeHandler(this);
+ view.getPrimaryAddressField().addValueChangeHandler(this);
+ view.getPrimaryAddress2Field().addValueChangeHandler(this);
+ view.getPrimaryCityField().addValueChangeHandler(this);
+ view.getPrimaryStateField().addValueChangeHandler(this);
+ view.getPrimaryZipField().addValueChangeHandler(this);
+ view.getPrimaryContactField().addValueChangeHandler(this);
+ view.getPrimaryPhoneField().addValueChangeHandler(this);
+ view.getPrimaryEmailField().addValueChangeHandler(this);
+ view.getSecondaryContactField().addValueChangeHandler(this);
+ view.getSecondaryPhoneField().addValueChangeHandler(this);
+ view.getSecondaryEmailField().addValueChangeHandler(this);
+ view.getNotesField().addValueChangeHandler(this);
+
+ view.getSaveButton().addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ saveClient();
+ }
+ });
+ }
+
+ protected void setView(ClientDTO client) {
+ view.getIdentifierField().setValue(client.getIdentifier());
+ view.getNameField().setValue(client.getName());
+ view.getPrimaryAddressField().setValue(client.getPrimaryAddress());
+ view.getPrimaryAddress2Field().setValue(client.getPrimaryAddress2());
+ view.getPrimaryCityField().setValue(client.getPrimaryCity());
+ view.getPrimaryStateField().setValue(client.getPrimaryState());
+ view.getPrimaryZipField().setValue(client.getPrimaryZip());
+ view.getPrimaryContactField().setValue(client.getPrimaryContactName());
+ view.getPrimaryPhoneField().setValue(client.getPrimaryPhone());
+ view.getPrimaryEmailField().setValue(client.getPrimaryEmail());
+ view.getSecondaryContactField().setValue(client.getSecondaryContactName());
+ view.getSecondaryPhoneField().setValue(client.getSecondaryPhone());
+ view.getSecondaryEmailField().setValue(client.getSecondaryEmail());
+ view.getNotesField().setValue(client.getNotes());
+ }
+
+ protected void getView(ClientDTO client) {
+ client.setIdentifier(view.getIdentifierField().getValue());
+ client.setName(view.getNameField().getValue());
+ client.setPrimaryAddress(view.getPrimaryAddressField().getValue());
+ client.setPrimaryAddress2(view.getPrimaryAddress2Field().getValue());
+ client.setPrimaryCity(view.getPrimaryCityField().getValue());
+ client.setPrimaryState(view.getPrimaryStateField().getValue());
+ client.setPrimaryZip(view.getPrimaryZipField().getValue());
+ client.setPrimaryContactName(view.getPrimaryContactField().getValue());
+ client.setPrimaryPhone(view.getPrimaryPhoneField().getValue());
+ client.setPrimaryEmail(view.getPrimaryEmailField().getValue());
+ client.setSecondaryContactName(view.getSecondaryContactField().getValue());
+ client.setSecondaryPhone(view.getSecondaryPhoneField().getValue());
+ client.setSecondaryEmail(view.getSecondaryEmailField().getValue());
+ client.setNotes(view.getNotesField().getValue());
+ }
+
+ private void saveClient() {
+ getView(client);
+
+ SaveClientAction action = new SaveClientAction();
+ action.setClient(client);
+
+ biomedService.execute(action, new DefaultCallback() {
+
+ @Override
+ public void onSuccess(EmptyResult result) {
+ resetDirtyFlag();
+ onSaveComplete();
+ }
+ });
+ }
+
+ protected void onSaveComplete() {
+
+ }
+}
diff --git a/src/com/biomed/client/activity/EditClientDevicesActivity.java b/src/com/biomed/client/activity/EditClientDevicesActivity.java
new file mode 100644
index 0000000..ce69f4e
--- /dev/null
+++ b/src/com/biomed/client/activity/EditClientDevicesActivity.java
@@ -0,0 +1,53 @@
+package com.biomed.client.activity;
+
+import com.biomed.client.BiomedActivityManager.SiteContainer;
+import com.biomed.client.BiomedSite;
+import com.biomed.client.dispatch.DefaultCallback;
+import com.biomed.client.services.BiomedService;
+import com.biomed.client.ui.clienteditor.ClientDevicesEditorPanel;
+import com.biomed.client.ui.clienteditor.ClientEditorSidebarPanel.Tabs;
+import com.biomed.shared.api.GetDevicesAction;
+import com.biomed.shared.api.ListResult;
+import com.biomed.shared.api.dto.DeviceDTO;
+import com.google.gwt.view.client.ListDataProvider;
+import com.google.inject.Inject;
+
+public class EditClientDevicesActivity extends BaseClientActivity {
+
+ @Inject
+ ClientDevicesEditorPanel view;
+ @Inject
+ BiomedService biomedService;
+
+ private ListDataProvider dataProvider;
+
+ @Override
+ protected boolean showNavigationPanel() {
+ return true;
+ }
+
+ @Override
+ protected Tabs getTab() {
+ return Tabs.DEVICES;
+ }
+
+ @Override
+ protected void createContentPanel(SiteContainer panel) {
+ dataProvider = new ListDataProvider();
+ dataProvider.addDataDisplay(view.getTable());
+
+ panel.setWidget(BiomedSite.CONTENT, view);
+
+ view.showLoading();
+
+ GetDevicesAction action = new GetDevicesAction();
+ action.setClientId(getClientId());
+
+ biomedService.execute(action, new DefaultCallback>() {
+ @Override
+ public void onSuccess(ListResult result) {
+ dataProvider.setList(result.getResult());
+ }
+ });
+ }
+}
diff --git a/src/com/biomed/client/activity/EditClientFrequencyActivity.java b/src/com/biomed/client/activity/EditClientFrequencyActivity.java
new file mode 100644
index 0000000..b70391a
--- /dev/null
+++ b/src/com/biomed/client/activity/EditClientFrequencyActivity.java
@@ -0,0 +1,66 @@
+package com.biomed.client.activity;
+
+import com.biomed.client.BiomedActivityManager.SiteContainer;
+import com.biomed.client.BiomedSite;
+import com.biomed.client.dispatch.DefaultCallback;
+import com.biomed.client.services.BiomedService;
+import com.biomed.client.ui.clienteditor.ClientEditorSidebarPanel.Tabs;
+import com.biomed.client.ui.clienteditor.ClientFrequencyEditorPanel;
+import com.biomed.shared.api.EmptyResult;
+import com.biomed.shared.api.GetClientFrequencyAction;
+import com.biomed.shared.api.SaveClientFrequenciesAction;
+import com.biomed.shared.api.SingleResult;
+import com.biomed.shared.api.dto.FrequencyDTO;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.inject.Inject;
+
+public class EditClientFrequencyActivity extends BaseClientActivity {
+
+ @Inject
+ ClientFrequencyEditorPanel view;
+ @Inject
+ BiomedService biomedService;
+
+ @Override
+ protected boolean showNavigationPanel() {
+ return true;
+ }
+
+ @Override
+ protected Tabs getTab() {
+ return Tabs.FREQUENCY;
+ }
+
+ @Override
+ protected void createContentPanel(SiteContainer panel) {
+ panel.setWidget(BiomedSite.CONTENT, view);
+
+ GetClientFrequencyAction action = new GetClientFrequencyAction();
+ action.setClientId(getClientId());
+
+ biomedService.execute(action, new DefaultCallback>() {
+ @Override
+ public void onSuccess(SingleResult result) {
+ view.setFrequencies(result.getResult());
+ }
+ });
+
+ view.getSaveButton().addClickHandler(new ClickHandler() {
+
+ @Override
+ public void onClick(ClickEvent event) {
+ SaveClientFrequenciesAction action = new SaveClientFrequenciesAction();
+ action.setClientId(getClientId());
+ action.setFrequencies(view.getFrequencies());
+
+ biomedService.execute(action, new DefaultCallback() {
+ @Override
+ public void onSuccess(EmptyResult result) {
+
+ }
+ });
+ }
+ });
+ }
+}
diff --git a/src/com/biomed/client/activity/EditClientOverviewActivity.java b/src/com/biomed/client/activity/EditClientOverviewActivity.java
new file mode 100644
index 0000000..852f0c5
--- /dev/null
+++ b/src/com/biomed/client/activity/EditClientOverviewActivity.java
@@ -0,0 +1,43 @@
+package com.biomed.client.activity;
+
+import com.biomed.client.BiomedActivityManager.SiteContainer;
+import com.biomed.client.BiomedSite;
+import com.biomed.client.dispatch.DefaultCallback;
+import com.biomed.client.ui.clienteditor.ClientEditorSidebarPanel.Tabs;
+import com.biomed.shared.api.GetClientAction;
+import com.biomed.shared.api.SingleResult;
+import com.biomed.shared.api.dto.ClientDTO;
+
+public class EditClientOverviewActivity extends BaseClientOverviewActivity {
+
+ @Override
+ protected boolean showNavigationPanel() {
+ return true;
+ }
+
+ @Override
+ protected Tabs getTab() {
+ return Tabs.OVERVIEW;
+ }
+
+ @Override
+ protected void createContentPanel(final SiteContainer panel) {
+ setupEvents();
+ fetchClient(panel, getClientId());
+ }
+
+ private void fetchClient(final SiteContainer panel, int id) {
+ GetClientAction action = new GetClientAction();
+ action.setClientId(id);
+
+ biomedService.execute(action, new DefaultCallback>() {
+
+ @Override
+ public void onSuccess(SingleResult result) {
+ panel.setWidget(BiomedSite.CONTENT, view);
+ client = result.getResult();
+ setView(client);
+ }
+ });
+ }
+}
diff --git a/src/com/biomed/client/activity/EditClientWorkordersActivity.java b/src/com/biomed/client/activity/EditClientWorkordersActivity.java
new file mode 100644
index 0000000..dfc9f48
--- /dev/null
+++ b/src/com/biomed/client/activity/EditClientWorkordersActivity.java
@@ -0,0 +1,60 @@
+package com.biomed.client.activity;
+
+import com.biomed.client.BiomedActivityManager.SiteContainer;
+import com.biomed.client.BiomedSite;
+import com.biomed.client.dispatch.DefaultCallback;
+import com.biomed.client.place.EditWorkorderPlace;
+import com.biomed.client.services.BiomedService;
+import com.biomed.client.ui.clienteditor.ClientEditorSidebarPanel.Tabs;
+import com.biomed.client.ui.clienteditor.ClientWorkordersEditorPanel;
+import com.biomed.shared.api.GetWorkordersAction;
+import com.biomed.shared.api.ListResult;
+import com.biomed.shared.api.dto.WorkorderDTO;
+import com.google.gwt.view.client.ListDataProvider;
+import com.google.inject.Inject;
+
+public class EditClientWorkordersActivity extends BaseClientActivity {
+
+ @Inject
+ ClientWorkordersEditorPanel view;
+ @Inject
+ BiomedService biomedService;
+
+ private ListDataProvider dataProvider;
+
+ @Override
+ protected boolean showNavigationPanel() {
+ return true;
+ }
+
+ @Override
+ protected Tabs getTab() {
+ return Tabs.WORKORDERS;
+ }
+
+ @Override
+ protected void createContentPanel(SiteContainer panel) {
+ dataProvider = new ListDataProvider();
+ dataProvider.addDataDisplay(view.getTable());
+
+ view.activity = this;
+
+ panel.setWidget(BiomedSite.CONTENT, view);
+
+ view.showLoading();
+
+ GetWorkordersAction action = new GetWorkordersAction();
+ action.setClientId(getClientId());
+
+ biomedService.execute(action, new DefaultCallback>() {
+ @Override
+ public void onSuccess(ListResult result) {
+ dataProvider.setList(result.getResult());
+ }
+ });
+ }
+
+ public void onWorkorderClicked(int id) {
+ placeController.goTo(EditWorkorderPlace.getPlace(id));
+ }
+}
diff --git a/src/com/biomed/client/activity/EditWorkorderActivity.java b/src/com/biomed/client/activity/EditWorkorderActivity.java
new file mode 100644
index 0000000..6d07e89
--- /dev/null
+++ b/src/com/biomed/client/activity/EditWorkorderActivity.java
@@ -0,0 +1,136 @@
+package com.biomed.client.activity;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+import com.biomed.client.AbstractBiomedActivity;
+import com.biomed.client.BiomedActivityManager.SiteContainer;
+import com.biomed.client.BiomedSite;
+import com.biomed.client.dispatch.DefaultCallback;
+import com.biomed.client.place.EditWorkorderPlace;
+import com.biomed.client.services.BiomedService;
+import com.biomed.client.ui.workorders.EditWorkorderPanel;
+import com.biomed.shared.api.DeleteWorkorderAction;
+import com.biomed.shared.api.EmptyResult;
+import com.biomed.shared.api.GetUsersAction;
+import com.biomed.shared.api.GetWorkorderViewAction;
+import com.biomed.shared.api.ListResult;
+import com.biomed.shared.api.SaveWorkorderAction;
+import com.biomed.shared.api.SingleResult;
+import com.biomed.shared.api.dto.EditWorkorderViewDTO;
+import com.biomed.shared.api.dto.ScheduleTime;
+import com.biomed.shared.api.dto.UserDTO;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.History;
+import com.google.gwt.user.client.Window;
+import com.google.web.bindery.event.shared.EventBus;
+
+public class EditWorkorderActivity extends AbstractBiomedActivity {
+
+ @Inject
+ BiomedService biomedService;
+
+ @Inject
+ EditWorkorderPanel view;
+
+ private List techs;
+
+ @Override
+ public void start(SiteContainer container, EventBus eventBus) {
+ container.setWidget(BiomedSite.CONTENT, view);
+
+ view.getStartTimeField().setValue(ScheduleTime.H8M00A);
+ view.getStartTimeField().setAcceptableValues(ScheduleTime.getValues());
+
+ view.getEndTimeField().setValue(ScheduleTime.H5M00P);
+ view.getEndTimeField().setAcceptableValues(ScheduleTime.getValues());
+
+ view.getSaveButton().addClickHandler(new ClickHandler() {
+
+ @Override
+ public void onClick(ClickEvent event) {
+ SaveWorkorderAction action = new SaveWorkorderAction();
+ action.setWorkorderReason(view.getWorkorderReasonField().getValue());
+ action.setWorkorderStatus(view.getWorkorderStatusField().getValue());
+ action.setActionTaken(view.getActionTakenField().getValue());
+ action.setRemarks(view.getRemarksField().getValue());
+ action.setId(getWorkorderId());
+ action.setTechId(view.getTechField().getValue().getId());
+ action.setJobDate(view.getWorkorderJobDateField().getValue());
+ action.setStartTime(view.getStartTimeField().getValue());
+ action.setEndTime(view.getEndTimeField().getValue());
+
+ biomedService.execute(action, new DefaultCallback() {
+ @Override
+ public void onSuccess(EmptyResult result) {
+ }
+ });
+ }
+
+ });
+
+ view.getDeleteButton().addClickHandler(new ClickHandler() {
+
+ @Override
+ public void onClick(ClickEvent event) {
+ if (Window.confirm("Are you sure you wish to delete this workorder?")) {
+ DeleteWorkorderAction action = new DeleteWorkorderAction();
+ action.setWorkorderId(getWorkorderId());
+
+ biomedService.execute(action, new DefaultCallback() {
+ @Override
+ public void onSuccess(EmptyResult result) {
+ History.back();
+ }
+ });
+ }
+ }
+ });
+
+ loadTechList();
+ }
+
+ private void loadTechList() {
+ GetUsersAction action = new GetUsersAction();
+ action.setUserTypeId(1);
+
+ biomedService.execute(action, new DefaultCallback>() {
+ @Override
+ public void onSuccess(ListResult result) {
+ techs = result.getResult();
+ view.getTechField().setAcceptableValues(techs);
+ loadWorkorder();
+ }
+ });
+ }
+
+ private void loadWorkorder() {
+ GetWorkorderViewAction action = new GetWorkorderViewAction();
+ action.setWorkorderId(getWorkorderId());
+
+ biomedService.execute(action, new DefaultCallback>() {
+ @Override
+ public void onSuccess(SingleResult result) {
+ EditWorkorderViewDTO data = result.getResult();
+ view.setStaticValues(data);
+ view.getStartTimeField().setValue(data.getWorkorderStartTime());
+ view.getEndTimeField().setValue(data.getWorkorderEndTime());
+ view.getWorkorderJobDateField().setValue(data.getWorkorderJobDate());
+
+ for (UserDTO tech : techs) {
+ if (tech.getId() == data.getTechId()) {
+ view.getTechField().setValue(tech);
+ break;
+ }
+ }
+ }
+ });
+ }
+
+ protected int getWorkorderId() {
+ return ((EditWorkorderPlace) place).getId();
+ }
+
+}
diff --git a/src/com/biomed/client/activity/NewClientActivity.java b/src/com/biomed/client/activity/NewClientActivity.java
new file mode 100644
index 0000000..3b0142c
--- /dev/null
+++ b/src/com/biomed/client/activity/NewClientActivity.java
@@ -0,0 +1,36 @@
+package com.biomed.client.activity;
+
+import javax.inject.Inject;
+
+import com.biomed.client.BiomedActivityManager.SiteContainer;
+import com.biomed.client.BiomedSite;
+import com.biomed.client.place.ViewClientsPlace;
+import com.biomed.client.services.BiomedService;
+import com.biomed.shared.api.dto.ClientDTO;
+import com.google.gwt.place.shared.PlaceController;
+
+public class NewClientActivity extends BaseClientOverviewActivity {
+
+ @Inject PlaceController placeController;
+ @Inject BiomedService biomedService;
+
+ @Override
+ protected boolean showNavigationPanel() {
+ return false;
+ }
+
+ @Override
+ protected void createContentPanel(final SiteContainer panel) {
+ setupEvents();
+
+ client = new ClientDTO();
+ setView(client);
+
+ panel.setWidget(BiomedSite.CONTENT, view);
+ }
+
+ @Override
+ protected void onSaveComplete() {
+ placeController.goTo(ViewClientsPlace.INSTANCE);
+ }
+}
diff --git a/src/com/biomed/client/activity/NewMessageActivity.java b/src/com/biomed/client/activity/NewMessageActivity.java
new file mode 100644
index 0000000..62d7866
--- /dev/null
+++ b/src/com/biomed/client/activity/NewMessageActivity.java
@@ -0,0 +1,63 @@
+package com.biomed.client.activity;
+
+import java.util.Arrays;
+
+import javax.inject.Inject;
+
+import com.biomed.client.AbstractBiomedActivity;
+import com.biomed.client.BiomedActivityManager.SiteContainer;
+import com.biomed.client.BiomedSite;
+import com.biomed.client.dispatch.DefaultCallback;
+import com.biomed.client.services.BiomedService;
+import com.biomed.client.ui.messages.MessageFor;
+import com.biomed.client.ui.messages.MessagesPanel;
+import com.biomed.shared.api.EmptyResult;
+import com.biomed.shared.api.SendMessageAction;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.History;
+import com.google.web.bindery.event.shared.EventBus;
+
+public class NewMessageActivity extends AbstractBiomedActivity {
+
+ @Inject BiomedService biomedService;
+ @Inject MessagesPanel view;
+
+ @Override
+ public void start(SiteContainer container, EventBus eventBus) {
+ container.setWidget(BiomedSite.CONTENT, view);
+
+ view.getMessageForField().setValue(MessageFor.ChrisEndres);
+ view.getMessageForField().setAcceptableValues(Arrays.asList(MessageFor.values()));
+
+ view.getSendButton().addClickHandler(new ClickHandler() {
+
+ @Override
+ public void onClick(ClickEvent event) {
+ SendMessageAction action = new SendMessageAction();
+ action.setUserId(view.getMessageForField().getValue().getId());
+ action.setName(view.getNameField().getValue());
+ action.setCompany(view.getCompanyField().getValue());
+ action.setPhone(view.getPhoneField().getValue());
+ action.setPhoneExtension(view.getPhoneExtensionField().getValue());
+ action.setTimeToReturnCall(view.getTimeToReturnCallField().getValue());
+ action.setTelephoned(view.getTelephonedField().getValue());
+ action.setCameToSeeYou(view.getCameToSeeYouField().getValue());
+ action.setWantsToSeeYou(view.getWantsToSeeYouField().getValue());
+ action.setReturnedYourCall(view.getReturnedYourCallField().getValue());
+ action.setPleaseCall(view.getPleaseCallField().getValue());
+ action.setWillCallAgain(view.getWillCallAgainField().getValue());
+ action.setRush(view.getRushField().getValue());
+ action.setSpecialAttention(view.getSpecialAttentionField().getValue());
+
+ biomedService.execute(action, new DefaultCallback() {
+
+ @Override
+ public void onSuccess(EmptyResult result) {
+ History.back();
+ }
+ });
+ }
+ });
+ }
+}
diff --git a/src/com/biomed/client/activity/ViewClientsActivity.java b/src/com/biomed/client/activity/ViewClientsActivity.java
new file mode 100644
index 0000000..b150bfa
--- /dev/null
+++ b/src/com/biomed/client/activity/ViewClientsActivity.java
@@ -0,0 +1,72 @@
+package com.biomed.client.activity;
+
+import javax.inject.Inject;
+
+import com.biomed.client.AbstractBiomedActivity;
+import com.biomed.client.BiomedActivityManager.SiteContainer;
+import com.biomed.client.BiomedSite;
+import com.biomed.client.dispatch.DefaultCallback;
+import com.biomed.client.place.EditClientOverviewPlace;
+import com.biomed.client.place.NewClientPlace;
+import com.biomed.client.services.BiomedService;
+import com.biomed.client.ui.clientlist.ClientListPanel;
+import com.biomed.client.ui.clientlist.ClientListSidebarPanel;
+import com.biomed.shared.api.GetClientsAction;
+import com.biomed.shared.api.GetClientsAction.View;
+import com.biomed.shared.api.ListResult;
+import com.biomed.shared.api.dto.ClientDTO;
+import com.google.gwt.place.shared.PlaceController;
+import com.google.gwt.view.client.ListDataProvider;
+import com.google.web.bindery.event.shared.EventBus;
+
+public class ViewClientsActivity extends AbstractBiomedActivity {
+
+ @Inject ClientListPanel view;
+ @Inject ClientListSidebarPanel sidebar;
+ @Inject PlaceController placeController;
+ @Inject BiomedService biomedService;
+
+ private ListDataProvider dataProvider;
+ private String filter;
+
+ @Override
+ public void start(SiteContainer container, EventBus eventBus) {
+ view.activity = this;
+ sidebar.activity = this;
+
+ dataProvider = new ListDataProvider();
+ dataProvider.addDataDisplay(view.getTable());
+
+ container.setWidget(BiomedSite.CONTENT, view);
+ container.setWidget(BiomedSite.SIDEBAR, sidebar);
+ requestData();
+ }
+
+ public void onNewClientClicked() {
+ placeController.goTo(NewClientPlace.INSTANCE);
+ }
+
+ public void onClientClicked(int id) {
+ placeController.goTo(EditClientOverviewPlace.getPlace(id));
+ }
+
+ private void requestData() {
+ view.showLoading();
+
+ GetClientsAction action = new GetClientsAction();
+ action.setView(View.CLIENT_PREVIEW);
+ action.setFilter(filter);
+
+ biomedService.execute(action, new DefaultCallback>() {
+ @Override
+ public void onSuccess(ListResult result) {
+ dataProvider.setList(result.getResult());
+ }
+ });
+ }
+
+ public void filterTable(String filter) {
+ this.filter = filter;
+ requestData();
+ }
+}
diff --git a/src/com/biomed/client/activity/ViewScheduleActivity.java b/src/com/biomed/client/activity/ViewScheduleActivity.java
new file mode 100644
index 0000000..90cad31
--- /dev/null
+++ b/src/com/biomed/client/activity/ViewScheduleActivity.java
@@ -0,0 +1,214 @@
+package com.biomed.client.activity;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import com.biomed.client.AbstractBiomedActivity;
+import com.biomed.client.BiomedActivityManager.SiteContainer;
+import com.biomed.client.BiomedSite;
+import com.biomed.client.dispatch.DefaultCallback;
+import com.biomed.client.services.BiomedService;
+import com.biomed.client.ui.schedule.DateUtil;
+import com.biomed.client.ui.schedule.SchedulePanel;
+import com.biomed.client.ui.schedule.ScheduleSidebarPanel;
+import com.biomed.client.ui.schedule.Workorder;
+import com.biomed.shared.api.EmptyResult;
+import com.biomed.shared.api.GetClientsAction;
+import com.biomed.shared.api.GetClientsAction.View;
+import com.biomed.shared.api.GetUsersAction;
+import com.biomed.shared.api.ListResult;
+import com.biomed.shared.api.PropertyBag;
+import com.biomed.shared.api.ScheduleAction;
+import com.biomed.shared.api.SendTechScheduleEmailAction;
+import com.biomed.shared.api.SqlResultSet;
+import com.biomed.shared.api.dto.ClientDTO;
+import com.biomed.shared.api.dto.ScheduleTime;
+import com.biomed.shared.api.dto.UserDTO;
+import com.biomed.shared.api.dto.WorkorderReasonDTO;
+import com.biomed.shared.api.dto.WorkorderStatusDTO;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.i18n.shared.DateTimeFormat;
+import com.google.web.bindery.event.shared.EventBus;
+
+public class ViewScheduleActivity extends AbstractBiomedActivity {
+
+ private static final DateTimeFormat format = DateTimeFormat.getFormat("yyyy-MM-dd");
+
+ @Inject
+ SchedulePanel view;
+ @Inject
+ ScheduleSidebarPanel sidebar;
+ @Inject
+ BiomedService biomedService;
+
+ int colorIndex = 0;
+ private Map techColorMap;
+
+ private Date currentDate = new Date();
+ private int techId = 0;
+
+ @Override
+ public void start(SiteContainer container, EventBus eventBus) {
+ sidebar.activity = this;
+
+ container.setWidget(BiomedSite.CONTENT, view);
+ container.setWidget(BiomedSite.SIDEBAR, sidebar);
+
+ loadTechList();
+ loadClients();
+
+ sidebar.getWorkorderStatusField().setValue(WorkorderStatusDTO.PM_DUE);
+ sidebar.getWorkorderStatusField().setAcceptableValues(WorkorderStatusDTO.VALUES);
+
+ sidebar.getStartTimeField().setValue(ScheduleTime.H8M00A);
+ sidebar.getStartTimeField().setAcceptableValues(ScheduleTime.getValues());
+
+ sidebar.getEndTimeField().setValue(ScheduleTime.H5M00P);
+ sidebar.getEndTimeField().setAcceptableValues(ScheduleTime.getValues());
+
+ sidebar.getReasonField().setValue(WorkorderReasonDTO.PM_RESCHEDULE);
+ sidebar.getReasonField().setAcceptableValues(WorkorderReasonDTO.VALUES);
+
+ sidebar.getSaveButton().addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ saveWorkorder();
+ }
+ });
+ }
+
+ public void setTechId(int techId) {
+ this.techId = techId;
+ updateView();
+ }
+
+ public void setDate(Date currentDate) {
+ this.currentDate = currentDate;
+ updateView();
+ }
+
+ private void saveWorkorder() {
+ ScheduleAction action = new ScheduleAction();
+ action.setJobDate(sidebar.getJobDateField().getValue());
+ action.setClientId(sidebar.getClientField().getValue().getId());
+ action.setTechId(sidebar.getTechField().getValue().getId());
+ action.setStartTime(sidebar.getStartTimeField().getValue());
+ action.setEndTime(sidebar.getEndTimeField().getValue());
+ action.setReason(sidebar.getReasonField().getValue());
+ action.setRequestedBy(sidebar.getRequestedByField().getValue());
+ action.setRemarks(sidebar.getRemarksField().getValue());
+ action.setStatus(sidebar.getWorkorderStatusField().getValue());
+
+ biomedService.execute(action, new DefaultCallback() {
+
+ @Override
+ public void onSuccess(EmptyResult result) {
+ updateView();
+ }
+ });
+ }
+
+ public void sendReport() {
+ SendTechScheduleEmailAction action = new SendTechScheduleEmailAction();
+ action.setUserId(sidebar.getReportTechField().getValue().getId());
+ action.setDate(sidebar.getReportDateField().getValue());
+
+ biomedService.execute(action, new DefaultCallback() {
+
+ @Override
+ public void onSuccess(EmptyResult result) {
+ }
+ });
+ }
+
+ private void updateView() {
+ if (techId != 0) {
+ loadScheduleForTech(currentDate, techId);
+ } else {
+ loadScheduleForDay(currentDate);
+ }
+ }
+
+ private void loadTechList() {
+ GetUsersAction action = new GetUsersAction();
+ action.setUserTypeId(1);
+
+ biomedService.execute(action, new DefaultCallback>() {
+
+ @Override
+ public void onSuccess(ListResult result) {
+ colorIndex = 0;
+ techColorMap = Maps.newHashMap();
+
+ for (UserDTO user : result.getResult()) {
+ techColorMap.put(user.getId(), colorIndex);
+ colorIndex = (colorIndex + 1) % 12;
+ }
+
+ sidebar.setTechList(result.getResult());
+ loadScheduleForDay(DateUtil.getNextWorkDay(new Date()));
+ }
+ });
+ }
+
+ private void loadScheduleForDay(final Date day) {
+ biomedService.getSchedule(format.format(day), format.format(day), 0,
+ new DefaultCallback() {
+ @Override
+ public void onSuccess(SqlResultSet result) {
+ List workorders = Lists.newArrayList();
+ for (PropertyBag bag : result.results) {
+ Workorder workorder = new Workorder(bag);
+ workorder.color = techColorMap.get(workorder.techId);
+ workorders.add(workorder);
+ }
+ view.setView(day, 1);
+ view.addWorkorders(workorders);
+ }
+ });
+ }
+
+ private void loadScheduleForTech(final Date date, final int techId) {
+ final Date startDate = DateUtil.getWorkFirstDay(date);
+ final Date endDate = DateUtil.addDays(date, 5);
+
+ biomedService.getSchedule(format.format(startDate), format.format(endDate), techId,
+ new DefaultCallback() {
+ @Override
+ public void onSuccess(SqlResultSet result) {
+ List workorders = Lists.newArrayList();
+ for (PropertyBag bag : result.results) {
+ Workorder workorder = new Workorder(bag);
+ workorder.color = techColorMap.get(workorder.techId);
+ workorders.add(workorder);
+ }
+ view.setView(startDate, 5);
+ view.addWorkorders(workorders);
+ }
+ });
+ }
+
+ private void loadClients() {
+ GetClientsAction action = new GetClientsAction();
+ action.setView(View.NAMES_ONLY);
+
+ biomedService.execute(action, new DefaultCallback>() {
+
+ @Override
+ public void onSuccess(ListResult result) {
+ ArrayList results = result.getResult();
+ Collections.sort(results, new ClientDTO.IdentifierComparator());
+
+ sidebar.getClientField().setAcceptableValues(results);
+ }
+ });
+ }
+}
diff --git a/src/com/biomed/client/activity/ViewWorkordersActivity.java b/src/com/biomed/client/activity/ViewWorkordersActivity.java
new file mode 100644
index 0000000..160fdc8
--- /dev/null
+++ b/src/com/biomed/client/activity/ViewWorkordersActivity.java
@@ -0,0 +1,133 @@
+package com.biomed.client.activity;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+import javax.inject.Inject;
+
+import com.biomed.client.AbstractBiomedActivity;
+import com.biomed.client.BiomedActivityManager.SiteContainer;
+import com.biomed.client.BiomedSite;
+import com.biomed.client.dispatch.DefaultCallback;
+import com.biomed.client.place.EditWorkorderPlace;
+import com.biomed.client.services.BiomedService;
+import com.biomed.client.ui.workorders.WorkordersPanel;
+import com.biomed.client.ui.workorders.WorkordersSidebarPanel;
+import com.biomed.shared.api.GetClientsAction;
+import com.biomed.shared.api.GetClientsAction.View;
+import com.biomed.shared.api.GetUsersAction;
+import com.biomed.shared.api.GetWorkordersAction;
+import com.biomed.shared.api.ListResult;
+import com.biomed.shared.api.dto.ClientDTO;
+import com.biomed.shared.api.dto.UserDTO;
+import com.biomed.shared.api.dto.WorkorderDTO;
+import com.biomed.shared.api.dto.WorkorderStatusDTO;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.place.shared.PlaceController;
+import com.google.gwt.view.client.ListDataProvider;
+import com.google.web.bindery.event.shared.EventBus;
+
+public class ViewWorkordersActivity extends AbstractBiomedActivity {
+
+ @Inject
+ WorkordersPanel view;
+ @Inject
+ WorkordersSidebarPanel sidebar;
+ @Inject
+ BiomedService biomedService;
+ @Inject
+ PlaceController placeController;
+
+ private ListDataProvider dataProvider;
+
+ @Override
+ public void start(SiteContainer container, EventBus eventBus) {
+
+ view.activity = this;
+
+ dataProvider = new ListDataProvider();
+ dataProvider.addDataDisplay(view.getTable());
+
+ container.setWidget(BiomedSite.CONTENT, view);
+ container.setWidget(BiomedSite.SIDEBAR, sidebar);
+
+ sidebar.getSearchButton().addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ requestData();
+ }
+ });
+
+ sidebar.getWorkorderStatusField().setAcceptableValues(WorkorderStatusDTO.VALUES);
+
+ loadUsers();
+ loadClients();
+ }
+
+ public void onWorkorderClicked(int id) {
+ placeController.goTo(EditWorkorderPlace.getPlace(id));
+ }
+
+ private void loadUsers() {
+ GetUsersAction action = new GetUsersAction();
+ action.setUserTypeId(1);
+
+ biomedService.execute(action, new DefaultCallback>() {
+
+ @Override
+ public void onSuccess(ListResult result) {
+ ArrayList results = result.getResult();
+ Collections.sort(results, new UserDTO.DisplayNameComparator());
+
+ sidebar.getTechField().setAcceptableValues(results);
+ }
+ });
+ }
+
+ private void loadClients() {
+ GetClientsAction action = new GetClientsAction();
+ action.setView(View.NAMES_ONLY);
+
+ biomedService.execute(action, new DefaultCallback>() {
+
+ @Override
+ public void onSuccess(ListResult result) {
+ ArrayList results = result.getResult();
+ Collections.sort(results, new ClientDTO.IdentifierComparator());
+
+ sidebar.getClientField().setAcceptableValues(results);
+ }
+ });
+ }
+
+ private void requestData() {
+ view.showLoading();
+
+ GetWorkordersAction action = new GetWorkordersAction();
+ action.setStartDate(sidebar.getStartDateField().getValue());
+ action.setEndDate(sidebar.getEndDateField().getValue());
+
+ UserDTO tech = sidebar.getTechField().getValue();
+ if (tech != null) {
+ action.setTechId(tech.getId());
+ }
+
+ WorkorderStatusDTO status = sidebar.getWorkorderStatusField().getValue();
+ if (status != null) {
+ action.setWorkorderStatusId(status.getId());
+ }
+
+ ClientDTO client = sidebar.getClientField().getValue();
+ if (client != null) {
+ action.setClientId(client.getId());
+ }
+
+ biomedService.execute(action, new DefaultCallback>() {
+ @Override
+ public void onSuccess(ListResult result) {
+ dataProvider.setList(result.getResult());
+ }
+ });
+ }
+}
diff --git a/src/com/biomed/client/components/DatePicker.java b/src/com/biomed/client/components/DatePicker.java
new file mode 100644
index 0000000..6831499
--- /dev/null
+++ b/src/com/biomed/client/components/DatePicker.java
@@ -0,0 +1,17 @@
+package com.biomed.client.components;
+
+import java.util.Date;
+
+import com.github.gwtbootstrap.datepicker.client.ui.base.DateBoxBase;
+import com.google.gwt.user.client.ui.SimplePanel;
+
+public class DatePicker extends DateBoxBase {
+
+ private final SimplePanel panel;
+
+ public DatePicker() {
+ this.panel = new SimplePanel();
+ setElement(panel.getElement());
+ setValue(new Date());
+ }
+}
diff --git a/src/com/biomed/client/components/HeaderPanel.java b/src/com/biomed/client/components/HeaderPanel.java
new file mode 100644
index 0000000..b657a6b
--- /dev/null
+++ b/src/com/biomed/client/components/HeaderPanel.java
@@ -0,0 +1,16 @@
+package com.biomed.client.components;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.user.client.ui.Widget;
+
+public class HeaderPanel extends Widget {
+ interface Binder extends UiBinder {}
+
+ private static Binder binder = GWT.create(Binder.class);
+
+ public HeaderPanel() {
+ setElement(binder.createAndBindUi(this));
+ }
+}
diff --git a/src/com/biomed/client/components/HeaderPanel.ui.xml b/src/com/biomed/client/components/HeaderPanel.ui.xml
new file mode 100644
index 0000000..46711eb
--- /dev/null
+++ b/src/com/biomed/client/components/HeaderPanel.ui.xml
@@ -0,0 +1,14 @@
+
+
+
\ No newline at end of file
diff --git a/src/com/biomed/client/components/NavigationPanel.java b/src/com/biomed/client/components/NavigationPanel.java
new file mode 100644
index 0000000..38a00d9
--- /dev/null
+++ b/src/com/biomed/client/components/NavigationPanel.java
@@ -0,0 +1,93 @@
+package com.biomed.client.components;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import com.biomed.client.place.ClientsPlace;
+import com.biomed.client.place.MessagesPlace;
+import com.biomed.client.place.NewMessagePlace;
+import com.biomed.client.place.SchedulePlace;
+import com.biomed.client.place.ViewClientsPlace;
+import com.biomed.client.place.ViewSchedulePlace;
+import com.biomed.client.place.ViewWorkordersPlace;
+import com.biomed.client.place.WorkordersPlace;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.place.shared.Place;
+import com.google.gwt.place.shared.PlaceChangeEvent;
+import com.google.gwt.place.shared.PlaceController;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.web.bindery.event.shared.EventBus;
+
+@Singleton
+public class NavigationPanel extends Composite implements PlaceChangeEvent.Handler {
+ interface Binder extends UiBinder {}
+
+ private final PlaceController placeController;
+
+ @UiField Anchor scheduleLink;
+ @UiField Anchor clientsLink;
+ @UiField Anchor messagesLink;
+ @UiField Anchor workordersLink;
+
+ Anchor activeLink;
+
+ @Inject
+ public NavigationPanel(PlaceController placeController, EventBus eventBus, Binder binder) {
+ this.placeController = placeController;
+
+ eventBus.addHandler(PlaceChangeEvent.TYPE, this);
+
+ initWidget(binder.createAndBindUi(this));
+ }
+
+ @UiHandler("scheduleLink")
+ void onScheduleLinkClicked(ClickEvent event) {
+ placeController.goTo(ViewSchedulePlace.INSTANCE);
+ }
+
+ @UiHandler("clientsLink")
+ void onClientsLinkClicked(ClickEvent event) {
+ placeController.goTo(ViewClientsPlace.INSTANCE);
+ }
+
+ @UiHandler("messagesLink")
+ void onMessagesLinkClicked(ClickEvent event) {
+ placeController.goTo(NewMessagePlace.INSTANCE);
+ }
+
+ @UiHandler("workordersLink")
+ void onWorkordersLinkClicked(ClickEvent event) {
+ placeController.goTo(ViewWorkordersPlace.INSTANCE);
+ }
+
+ @Override
+ public void onPlaceChange(PlaceChangeEvent event) {
+ Place place = event.getNewPlace();
+
+ if (place instanceof ClientsPlace) {
+ setLinkActive(clientsLink);
+ } else if (place instanceof MessagesPlace) {
+ setLinkActive(messagesLink);
+ } else if (place instanceof SchedulePlace) {
+ setLinkActive(scheduleLink);
+ } else if (place instanceof WorkordersPlace) {
+ setLinkActive(workordersLink);
+ } else {
+ setLinkActive(null);
+ }
+ }
+
+ private void setLinkActive(Anchor link) {
+ if (activeLink != null) {
+ activeLink.removeStyleName("active");
+ }
+
+ activeLink = link;
+ activeLink.addStyleName("active");
+ }
+}
diff --git a/src/com/biomed/client/components/NavigationPanel.ui.xml b/src/com/biomed/client/components/NavigationPanel.ui.xml
new file mode 100644
index 0000000..1a813f0
--- /dev/null
+++ b/src/com/biomed/client/components/NavigationPanel.ui.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+ Schedule
+
+
+
+
+
+ Workorders
+
+
+
+
+
+ Clients
+
+
+
+
+
+ Messages
+
+
+
+
+
+ Tickets
+
+
+
+
+
\ No newline at end of file
diff --git a/src/com/biomed/client/components/Pager.java b/src/com/biomed/client/components/Pager.java
new file mode 100644
index 0000000..0fa59f4
--- /dev/null
+++ b/src/com/biomed/client/components/Pager.java
@@ -0,0 +1,251 @@
+package com.biomed.client.components;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.i18n.client.NumberFormat;
+import com.google.gwt.user.cellview.client.AbstractPager;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.view.client.HasRows;
+import com.google.gwt.view.client.Range;
+
+public class Pager extends AbstractPager {
+
+ private final Label label;
+ private final Image loading;
+
+ private final Anchor firstPage;
+ private final Anchor prevPage;
+ private final Anchor nextPage;
+ private final Anchor lastPage;
+
+ public Pager() {
+
+ label = new Label();
+ label.setStyleName("dataTables_info");
+
+ loading = new Image();
+ loading.setStyleName("loading");
+ loading.setUrl("resources/images/elements/loaders/9.gif");
+ loading.setVisible(false);
+
+ firstPage = new Anchor("First");
+ firstPage.setStyleName("first paginate_button");
+ firstPage.addClickHandler(new ClickHandler() {
+
+ @Override
+ public void onClick(ClickEvent event) {
+ firstPage();
+ }
+ });
+
+ prevPage = new Anchor("Previous");
+ prevPage.setStyleName("previous paginate_button");
+ prevPage.addClickHandler(new ClickHandler() {
+
+ @Override
+ public void onClick(ClickEvent event) {
+ previousPage();
+ }
+ });
+
+ nextPage = new Anchor("Next");
+ nextPage.setStyleName("next paginate_button");
+ nextPage.addClickHandler(new ClickHandler() {
+
+ @Override
+ public void onClick(ClickEvent event) {
+ nextPage();
+ }
+ });
+
+ lastPage = new Anchor("Last");
+ lastPage.setStyleName("last paginate_button");
+ lastPage.addClickHandler(new ClickHandler() {
+
+ @Override
+ public void onClick(ClickEvent event) {
+ lastPage();
+ }
+ });
+
+ FlowPanel buttonsPanel = new FlowPanel();
+ buttonsPanel.setStyleName("dataTables_paginate paging_full_numbers");
+ buttonsPanel.add(firstPage);
+ buttonsPanel.add(prevPage);
+ buttonsPanel.add(nextPage);
+ buttonsPanel.add(lastPage);
+
+ FlowPanel layout = new FlowPanel();
+ layout.setStyleName("tableFooter");
+
+ layout.add(loading);
+ layout.add(label);
+ layout.add(buttonsPanel);
+
+ initWidget(layout);
+
+ setDisplay(null);
+ }
+
+ @Override
+ public void setDisplay(HasRows display) {
+ boolean disableButtons = (display == null);
+ setNextPageButtonsDisabled(disableButtons);
+ setPrevPageButtonsDisabled(disableButtons);
+
+ super.setDisplay(display);
+ }
+
+ public void startLoading() {
+ getDisplay().setRowCount(0, true);
+ loading.setVisible(true);
+ label.setText("Loading...");
+ }
+
+ protected String createText() {
+ // Default text is 1 based.
+ NumberFormat formatter = NumberFormat.getFormat("#,###");
+ HasRows display = getDisplay();
+ Range range = display.getVisibleRange();
+ int pageStart = range.getStart() + 1;
+ int pageSize = range.getLength();
+ int dataSize = display.getRowCount();
+ int endIndex = Math.min(dataSize, pageStart + pageSize - 1);
+ endIndex = Math.max(pageStart, endIndex);
+ boolean exact = display.isRowCountExact();
+ return "Showing " + formatter.format(pageStart) + " to " + formatter.format(endIndex)
+ + (exact ? " of " : " of over ") + formatter.format(dataSize) + " entries";
+ }
+
+ @Override
+ protected void onRangeOrRowCountChanged() {
+ HasRows display = getDisplay();
+ label.setText(createText());
+ loading.setVisible(false);
+
+ // Update the prev and first buttons.
+ setPrevPageButtonsDisabled(!hasPreviousPage());
+
+ // Update the next and last buttons.
+ if (isRangeLimited() || !display.isRowCountExact()) {
+ setNextPageButtonsDisabled(!hasNextPage());
+// setFastForwardDisabled(!hasNextPages(getFastForwardPages()));
+ }
+ }
+
+ /**
+ * Enable or disable the next page buttons.
+ *
+ * @param disabled true to disable, false to enable
+ */
+ private void setNextPageButtonsDisabled(boolean disabled) {
+ if (disabled) {
+ lastPage.addStyleName("paginate_button_disabled");
+ nextPage.addStyleName("paginate_button_disabled");
+ } else {
+ lastPage.removeStyleName("paginate_button_disabled");
+ nextPage.removeStyleName("paginate_button_disabled");
+ }
+
+ lastPage.setEnabled(!disabled);
+ nextPage.setEnabled(!disabled);
+ }
+
+ /**
+ * Enable or disable the previous page buttons.
+ *
+ * @param disabled true to disable, false to enable
+ */
+ private void setPrevPageButtonsDisabled(boolean disabled) {
+ if (disabled) {
+ firstPage.addStyleName("paginate_button_disabled");
+ prevPage.addStyleName("paginate_button_disabled");
+ } else {
+ firstPage.removeStyleName("paginate_button_disabled");
+ prevPage.removeStyleName("paginate_button_disabled");
+ }
+
+ firstPage.setEnabled(!disabled);
+ prevPage.setEnabled(!disabled);
+ }
+
+
+ @Override
+ public void firstPage() {
+ super.firstPage();
+ }
+
+ @Override
+ public int getPage() {
+ return super.getPage();
+ }
+
+ @Override
+ public int getPageCount() {
+ return super.getPageCount();
+ }
+
+ @Override
+ public boolean hasNextPage() {
+ return super.hasNextPage();
+ }
+
+ @Override
+ public boolean hasNextPages(int pages) {
+ return super.hasNextPages(pages);
+ }
+
+ @Override
+ public boolean hasPage(int index) {
+ return super.hasPage(index);
+ }
+
+ @Override
+ public boolean hasPreviousPage() {
+ return super.hasPreviousPage();
+ }
+
+ @Override
+ public boolean hasPreviousPages(int pages) {
+ return super.hasPreviousPages(pages);
+ }
+
+ @Override
+ public void lastPage() {
+ super.lastPage();
+ }
+
+ @Override
+ public void lastPageStart() {
+ super.lastPageStart();
+ }
+
+ @Override
+ public void nextPage() {
+ super.nextPage();
+ }
+
+ @Override
+ public void previousPage() {
+ super.previousPage();
+ }
+
+
+ @Override
+ public void setPage(int index) {
+ super.setPage(index);
+ }
+
+ @Override
+ public void setPageSize(int pageSize) {
+ super.setPageSize(pageSize);
+ }
+
+ @Override
+ public void setPageStart(int index) {
+ super.setPageStart(index);
+ }
+}
diff --git a/src/com/biomed/client/dispatch/ClientDispatchGinModule.java b/src/com/biomed/client/dispatch/ClientDispatchGinModule.java
new file mode 100644
index 0000000..4a70ae6
--- /dev/null
+++ b/src/com/biomed/client/dispatch/ClientDispatchGinModule.java
@@ -0,0 +1,14 @@
+package com.biomed.client.dispatch;
+
+import javax.inject.Singleton;
+
+import com.biomed.shared.dispatch.DispatchAsync;
+import com.google.gwt.inject.client.AbstractGinModule;
+
+public class ClientDispatchGinModule extends AbstractGinModule {
+
+ @Override
+ protected void configure() {
+ bind(DispatchAsync.class).to(DefaultDispatchAsync.class).in(Singleton.class);
+ }
+}
\ No newline at end of file
diff --git a/src/com/biomed/client/dispatch/DefaultCallback.java b/src/com/biomed/client/dispatch/DefaultCallback.java
new file mode 100644
index 0000000..c9a3ce0
--- /dev/null
+++ b/src/com/biomed/client/dispatch/DefaultCallback.java
@@ -0,0 +1,10 @@
+package com.biomed.client.dispatch;
+
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+public abstract class DefaultCallback implements AsyncCallback {
+ @Override
+ public void onFailure(Throwable caught) {
+ throw new RuntimeException(caught);
+ }
+}
diff --git a/src/com/biomed/client/dispatch/DefaultDispatchAsync.java b/src/com/biomed/client/dispatch/DefaultDispatchAsync.java
new file mode 100644
index 0000000..62675c4
--- /dev/null
+++ b/src/com/biomed/client/dispatch/DefaultDispatchAsync.java
@@ -0,0 +1,31 @@
+package com.biomed.client.dispatch;
+
+import com.biomed.shared.dispatch.DispatchAction;
+import com.biomed.shared.dispatch.DispatchAsync;
+import com.biomed.shared.dispatch.DispatchService;
+import com.biomed.shared.dispatch.DispatchServiceAsync;
+import com.biomed.shared.dispatch.DispatchResult;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+public class DefaultDispatchAsync implements DispatchAsync {
+
+ private static final DispatchServiceAsync realService = GWT.create(DispatchService.class);
+
+ public DefaultDispatchAsync() {
+ }
+
+ public , R extends DispatchResult> void execute(final A action,
+ final AsyncCallback callback) {
+ realService.execute(action, new AsyncCallback() {
+ public void onFailure(Throwable caught) {
+ callback.onFailure(caught);
+ }
+
+ @SuppressWarnings("unchecked")
+ public void onSuccess(DispatchResult result) {
+ callback.onSuccess((R) result);
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/com/biomed/client/place/BiomedPlace.java b/src/com/biomed/client/place/BiomedPlace.java
new file mode 100644
index 0000000..c8c2cfe
--- /dev/null
+++ b/src/com/biomed/client/place/BiomedPlace.java
@@ -0,0 +1,5 @@
+package com.biomed.client.place;
+
+import com.google.gwt.place.shared.Place;
+
+public abstract class BiomedPlace extends Place { }
diff --git a/src/com/biomed/client/place/ClientsPlace.java b/src/com/biomed/client/place/ClientsPlace.java
new file mode 100644
index 0000000..477ff57
--- /dev/null
+++ b/src/com/biomed/client/place/ClientsPlace.java
@@ -0,0 +1,3 @@
+package com.biomed.client.place;
+
+public abstract class ClientsPlace extends BiomedPlace { }
diff --git a/src/com/biomed/client/place/EditClientDevicesPlace.java b/src/com/biomed/client/place/EditClientDevicesPlace.java
new file mode 100644
index 0000000..c6660db
--- /dev/null
+++ b/src/com/biomed/client/place/EditClientDevicesPlace.java
@@ -0,0 +1,46 @@
+package com.biomed.client.place;
+
+import com.google.gwt.place.shared.PlaceTokenizer;
+import com.google.gwt.place.shared.Prefix;
+
+public final class EditClientDevicesPlace extends ClientsPlace implements HasClientId {
+
+ public static EditClientDevicesPlace getPlace(int id) {
+ return new EditClientDevicesPlace(id);
+ }
+
+ private final int id;
+
+ private EditClientDevicesPlace(int id) {
+ this.id = id;
+ }
+
+ @Override
+ public int getId() {
+ return id;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (!(obj instanceof EditClientDevicesPlace)) return false;
+
+ EditClientDevicesPlace other = (EditClientDevicesPlace) obj;
+
+ return other.id == this.id;
+ }
+
+ @Prefix("client/device")
+ public static class Tokenizer implements PlaceTokenizer {
+
+ @Override
+ public EditClientDevicesPlace getPlace(String token) {
+ return EditClientDevicesPlace.getPlace(Integer.parseInt(token));
+ }
+
+ @Override
+ public String getToken(EditClientDevicesPlace place) {
+ return Integer.toString(place.id);
+ }
+ }
+}
diff --git a/src/com/biomed/client/place/EditClientFrequencyPlace.java b/src/com/biomed/client/place/EditClientFrequencyPlace.java
new file mode 100644
index 0000000..ef83701
--- /dev/null
+++ b/src/com/biomed/client/place/EditClientFrequencyPlace.java
@@ -0,0 +1,46 @@
+package com.biomed.client.place;
+
+import com.google.gwt.place.shared.PlaceTokenizer;
+import com.google.gwt.place.shared.Prefix;
+
+public final class EditClientFrequencyPlace extends ClientsPlace implements HasClientId {
+
+ public static EditClientFrequencyPlace getPlace(int id) {
+ return new EditClientFrequencyPlace(id);
+ }
+
+ private final int id;
+
+ private EditClientFrequencyPlace(int id) {
+ this.id = id;
+ }
+
+ @Override
+ public int getId() {
+ return id;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (!(obj instanceof EditClientFrequencyPlace)) return false;
+
+ EditClientFrequencyPlace other = (EditClientFrequencyPlace) obj;
+
+ return other.id == this.id;
+ }
+
+ @Prefix("client/frequency")
+ public static class Tokenizer implements PlaceTokenizer {
+
+ @Override
+ public EditClientFrequencyPlace getPlace(String token) {
+ return EditClientFrequencyPlace.getPlace(Integer.parseInt(token));
+ }
+
+ @Override
+ public String getToken(EditClientFrequencyPlace place) {
+ return Integer.toString(place.id);
+ }
+ }
+}
diff --git a/src/com/biomed/client/place/EditClientOverviewPlace.java b/src/com/biomed/client/place/EditClientOverviewPlace.java
new file mode 100644
index 0000000..4e5aef8
--- /dev/null
+++ b/src/com/biomed/client/place/EditClientOverviewPlace.java
@@ -0,0 +1,46 @@
+package com.biomed.client.place;
+
+import com.google.gwt.place.shared.PlaceTokenizer;
+import com.google.gwt.place.shared.Prefix;
+
+public final class EditClientOverviewPlace extends ClientsPlace implements HasClientId {
+
+ public static EditClientOverviewPlace getPlace(int id) {
+ return new EditClientOverviewPlace(id);
+ }
+
+ private final int id;
+
+ private EditClientOverviewPlace(int id) {
+ this.id = id;
+ }
+
+ @Override
+ public int getId() {
+ return id;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (!(obj instanceof EditClientOverviewPlace)) return false;
+
+ EditClientOverviewPlace other = (EditClientOverviewPlace) obj;
+
+ return other.id == this.id;
+ }
+
+ @Prefix("client/overview")
+ public static class Tokenizer implements PlaceTokenizer {
+
+ @Override
+ public EditClientOverviewPlace getPlace(String token) {
+ return EditClientOverviewPlace.getPlace(Integer.parseInt(token));
+ }
+
+ @Override
+ public String getToken(EditClientOverviewPlace place) {
+ return Integer.toString(place.id);
+ }
+ }
+}
diff --git a/src/com/biomed/client/place/EditClientWorkordersPlace.java b/src/com/biomed/client/place/EditClientWorkordersPlace.java
new file mode 100644
index 0000000..3457b8f
--- /dev/null
+++ b/src/com/biomed/client/place/EditClientWorkordersPlace.java
@@ -0,0 +1,46 @@
+package com.biomed.client.place;
+
+import com.google.gwt.place.shared.PlaceTokenizer;
+import com.google.gwt.place.shared.Prefix;
+
+public final class EditClientWorkordersPlace extends ClientsPlace implements HasClientId {
+
+ public static EditClientWorkordersPlace getPlace(int id) {
+ return new EditClientWorkordersPlace(id);
+ }
+
+ private final int id;
+
+ private EditClientWorkordersPlace(int id) {
+ this.id = id;
+ }
+
+ @Override
+ public int getId() {
+ return id;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (!(obj instanceof EditClientWorkordersPlace)) return false;
+
+ EditClientWorkordersPlace other = (EditClientWorkordersPlace) obj;
+
+ return other.id == this.id;
+ }
+
+ @Prefix("client/workorders")
+ public static class Tokenizer implements PlaceTokenizer {
+
+ @Override
+ public EditClientWorkordersPlace getPlace(String token) {
+ return EditClientWorkordersPlace.getPlace(Integer.parseInt(token));
+ }
+
+ @Override
+ public String getToken(EditClientWorkordersPlace place) {
+ return Integer.toString(place.id);
+ }
+ }
+}
diff --git a/src/com/biomed/client/place/EditWorkorderPlace.java b/src/com/biomed/client/place/EditWorkorderPlace.java
new file mode 100644
index 0000000..9204e86
--- /dev/null
+++ b/src/com/biomed/client/place/EditWorkorderPlace.java
@@ -0,0 +1,45 @@
+package com.biomed.client.place;
+
+import com.google.gwt.place.shared.PlaceTokenizer;
+import com.google.gwt.place.shared.Prefix;
+
+public class EditWorkorderPlace extends WorkordersPlace {
+
+ public static EditWorkorderPlace getPlace(int id) {
+ return new EditWorkorderPlace(id);
+ }
+
+ private final int id;
+
+ private EditWorkorderPlace(int id) {
+ this.id = id;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (!(obj instanceof EditWorkorderPlace)) return false;
+
+ EditWorkorderPlace other = (EditWorkorderPlace) obj;
+
+ return other.id == this.id;
+ }
+
+ @Prefix("workorder/edit")
+ public static class Tokenizer implements PlaceTokenizer {
+
+ @Override
+ public EditWorkorderPlace getPlace(String token) {
+ return EditWorkorderPlace.getPlace(Integer.parseInt(token));
+ }
+
+ @Override
+ public String getToken(EditWorkorderPlace place) {
+ return Integer.toString(place.id);
+ }
+ }
+}
diff --git a/src/com/biomed/client/place/HasClientId.java b/src/com/biomed/client/place/HasClientId.java
new file mode 100644
index 0000000..4efd6cb
--- /dev/null
+++ b/src/com/biomed/client/place/HasClientId.java
@@ -0,0 +1,5 @@
+package com.biomed.client.place;
+
+public interface HasClientId {
+ public int getId();
+}
diff --git a/src/com/biomed/client/place/MessagesPlace.java b/src/com/biomed/client/place/MessagesPlace.java
new file mode 100644
index 0000000..50ebbbe
--- /dev/null
+++ b/src/com/biomed/client/place/MessagesPlace.java
@@ -0,0 +1,3 @@
+package com.biomed.client.place;
+
+public abstract class MessagesPlace extends BiomedPlace { }
diff --git a/src/com/biomed/client/place/NewClientPlace.java b/src/com/biomed/client/place/NewClientPlace.java
new file mode 100644
index 0000000..cabed3d
--- /dev/null
+++ b/src/com/biomed/client/place/NewClientPlace.java
@@ -0,0 +1,23 @@
+package com.biomed.client.place;
+
+import com.google.gwt.place.shared.PlaceTokenizer;
+import com.google.gwt.place.shared.Prefix;
+
+public final class NewClientPlace extends ClientsPlace {
+ public static final NewClientPlace INSTANCE = new NewClientPlace();
+ private NewClientPlace() { }
+
+ @Prefix("client/new")
+ public static class Tokenizer implements PlaceTokenizer {
+
+ @Override
+ public NewClientPlace getPlace(String token) {
+ return NewClientPlace.INSTANCE;
+ }
+
+ @Override
+ public String getToken(NewClientPlace place) {
+ return "";
+ }
+ }
+}
diff --git a/src/com/biomed/client/place/NewMessagePlace.java b/src/com/biomed/client/place/NewMessagePlace.java
new file mode 100644
index 0000000..fba1268
--- /dev/null
+++ b/src/com/biomed/client/place/NewMessagePlace.java
@@ -0,0 +1,23 @@
+package com.biomed.client.place;
+
+import com.google.gwt.place.shared.PlaceTokenizer;
+import com.google.gwt.place.shared.Prefix;
+
+public final class NewMessagePlace extends MessagesPlace {
+ public static final NewMessagePlace INSTANCE = new NewMessagePlace();
+ private NewMessagePlace() { }
+
+ @Prefix("message/new")
+ public static class Tokenizer implements PlaceTokenizer {
+
+ @Override
+ public NewMessagePlace getPlace(String token) {
+ return NewMessagePlace.INSTANCE;
+ }
+
+ @Override
+ public String getToken(NewMessagePlace place) {
+ return "";
+ }
+ }
+}
diff --git a/src/com/biomed/client/place/SchedulePlace.java b/src/com/biomed/client/place/SchedulePlace.java
new file mode 100644
index 0000000..856ac7f
--- /dev/null
+++ b/src/com/biomed/client/place/SchedulePlace.java
@@ -0,0 +1,3 @@
+package com.biomed.client.place;
+
+public abstract class SchedulePlace extends BiomedPlace { }
diff --git a/src/com/biomed/client/place/ViewClientsPlace.java b/src/com/biomed/client/place/ViewClientsPlace.java
new file mode 100644
index 0000000..17ed84a
--- /dev/null
+++ b/src/com/biomed/client/place/ViewClientsPlace.java
@@ -0,0 +1,23 @@
+package com.biomed.client.place;
+
+import com.google.gwt.place.shared.PlaceTokenizer;
+import com.google.gwt.place.shared.Prefix;
+
+public final class ViewClientsPlace extends ClientsPlace {
+ public static final ViewClientsPlace INSTANCE = new ViewClientsPlace();
+ private ViewClientsPlace() { }
+
+ @Prefix("clients")
+ public static class Tokenizer implements PlaceTokenizer {
+
+ @Override
+ public ViewClientsPlace getPlace(String token) {
+ return ViewClientsPlace.INSTANCE;
+ }
+
+ @Override
+ public String getToken(ViewClientsPlace place) {
+ return "";
+ }
+ }
+}
diff --git a/src/com/biomed/client/place/ViewSchedulePlace.java b/src/com/biomed/client/place/ViewSchedulePlace.java
new file mode 100644
index 0000000..9eb369b
--- /dev/null
+++ b/src/com/biomed/client/place/ViewSchedulePlace.java
@@ -0,0 +1,23 @@
+package com.biomed.client.place;
+
+import com.google.gwt.place.shared.PlaceTokenizer;
+import com.google.gwt.place.shared.Prefix;
+
+public final class ViewSchedulePlace extends SchedulePlace {
+ public static final ViewSchedulePlace INSTANCE = new ViewSchedulePlace();
+ private ViewSchedulePlace() { }
+
+ @Prefix("schedule")
+ public static class Tokenizer implements PlaceTokenizer {
+
+ @Override
+ public ViewSchedulePlace getPlace(String token) {
+ return ViewSchedulePlace.INSTANCE;
+ }
+
+ @Override
+ public String getToken(ViewSchedulePlace place) {
+ return "";
+ }
+ }
+}
diff --git a/src/com/biomed/client/place/ViewWorkordersPlace.java b/src/com/biomed/client/place/ViewWorkordersPlace.java
new file mode 100644
index 0000000..a43f949
--- /dev/null
+++ b/src/com/biomed/client/place/ViewWorkordersPlace.java
@@ -0,0 +1,23 @@
+package com.biomed.client.place;
+
+import com.google.gwt.place.shared.PlaceTokenizer;
+import com.google.gwt.place.shared.Prefix;
+
+public final class ViewWorkordersPlace extends WorkordersPlace {
+ public static final ViewWorkordersPlace INSTANCE = new ViewWorkordersPlace();
+ private ViewWorkordersPlace() { }
+
+ @Prefix("workorders")
+ public static class Tokenizer implements PlaceTokenizer {
+
+ @Override
+ public ViewWorkordersPlace getPlace(String token) {
+ return ViewWorkordersPlace.INSTANCE;
+ }
+
+ @Override
+ public String getToken(ViewWorkordersPlace place) {
+ return "";
+ }
+ }
+}
diff --git a/src/com/biomed/client/place/WorkordersPlace.java b/src/com/biomed/client/place/WorkordersPlace.java
new file mode 100644
index 0000000..a3a6a06
--- /dev/null
+++ b/src/com/biomed/client/place/WorkordersPlace.java
@@ -0,0 +1,3 @@
+package com.biomed.client.place;
+
+public abstract class WorkordersPlace extends BiomedPlace { }
diff --git a/src/com/biomed/client/resources/BiomedAppointmentStyle.java b/src/com/biomed/client/resources/BiomedAppointmentStyle.java
new file mode 100644
index 0000000..6d50fe5
--- /dev/null
+++ b/src/com/biomed/client/resources/BiomedAppointmentStyle.java
@@ -0,0 +1,158 @@
+package com.biomed.client.resources;
+
+import com.bradrydzewski.gwt.calendar.client.ThemeAppointmentStyle;
+
+class BiomedAppointmentStyle implements ThemeAppointmentStyle {
+
+ public BiomedAppointmentStyle(String border, String background) {
+ super();
+
+ // set the border
+ this.border = "#979797";
+ this.selectedBorder = border;
+
+ // set the body text
+ this.text = "#777";
+ this.selectedText = text;
+
+ // set the header text
+ this.headerText = "#1D1D1D";
+ this.selectedHeaderText = headerText;
+
+ // set the background colors
+ this.background = background;
+ this.selectedBackground = background;
+
+ // set the header colors to the same color as the border
+ this.backgroundHeader = background;
+ this.selectedBackgroundHeader = background;
+
+ }
+
+ protected String selectedBorder;
+ protected String selectedBackground;
+ protected String selectedBackgroundImage;
+ protected String selectedBackgroundHeader;
+ protected String selectedBackgroundFooter;
+ protected String selectedText;
+ protected String selectedHeaderText;
+ protected String border;
+ protected String background;
+ protected String backgroundImage;
+ protected String backgroundHeader;
+ protected String backgroundFooter;
+ protected String text;
+ protected String headerText;
+
+ public String getSelectedBorder() {
+ return selectedBorder;
+ }
+
+ public String getSelectedBackground() {
+ return selectedBackground;
+ }
+
+ public String getSelectedBackgroundHeader() {
+ return selectedBackgroundHeader;
+ }
+
+ public String getSelectedBackgroundFooter() {
+ return selectedBackgroundFooter;
+ }
+
+ public String getSelectedText() {
+ return selectedText;
+ }
+
+ public String getSelectedHeaderText() {
+ return selectedHeaderText;
+ }
+
+ public String getBorder() {
+ return border;
+ }
+
+ public String getBackground() {
+ return background;
+ }
+
+ public String getBackgroundHeader() {
+ return backgroundHeader;
+ }
+
+ public String getBackgroundFooter() {
+ return backgroundFooter;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public String getHeaderText() {
+ return headerText;
+ }
+
+ public void setSelectedBorder(String selectedBorder) {
+ this.selectedBorder = selectedBorder;
+ }
+
+ public void setSelectedBackground(String selectedBackground) {
+ this.selectedBackground = selectedBackground;
+ }
+
+ public void setSelectedBackgroundHeader(String selectedBackgroundHeader) {
+ this.selectedBackgroundHeader = selectedBackgroundHeader;
+ }
+
+ public void setSelectedBackgroundFooter(String selectedBackgroundFooter) {
+ this.selectedBackgroundFooter = selectedBackgroundFooter;
+ }
+
+ public void setSelectedText(String selectedText) {
+ this.selectedText = selectedText;
+ }
+
+ public void setSelectedHeaderText(String selectedHeaderText) {
+ this.selectedHeaderText = selectedHeaderText;
+ }
+
+ public void setBorder(String border) {
+ this.border = border;
+ }
+
+ public void setBackground(String background) {
+ this.background = background;
+ }
+
+ public void setBackgroundHeader(String backgroundHeader) {
+ this.backgroundHeader = backgroundHeader;
+ }
+
+ public void setBackgroundFooter(String backgroundFooter) {
+ this.backgroundFooter = backgroundFooter;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ public void setHeaderText(String headerText) {
+ this.headerText = headerText;
+ }
+
+ public String getSelectedBackgroundImage() {
+ return selectedBackgroundImage;
+ }
+
+ public String getBackgroundImage() {
+ return backgroundImage;
+ }
+
+ public void setSelectedBackgroundImage(String selectedBackgroundImage) {
+ this.selectedBackgroundImage = selectedBackgroundImage;
+ }
+
+ public void setBackgroundImage(String backgroundImage) {
+ this.backgroundImage = backgroundImage;
+ }
+}
diff --git a/src/com/biomed/client/resources/BiomedAppointmentTheme.java b/src/com/biomed/client/resources/BiomedAppointmentTheme.java
new file mode 100644
index 0000000..77b5fe1
--- /dev/null
+++ b/src/com/biomed/client/resources/BiomedAppointmentTheme.java
@@ -0,0 +1,42 @@
+package com.biomed.client.resources;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class BiomedAppointmentTheme {
+ // border, background
+
+ public static final BiomedAppointmentStyle DARK_GRAY = new BiomedAppointmentStyle("#979797", "#C2C2C2");
+ public static final BiomedAppointmentStyle BOLD_BLUE = new BiomedAppointmentStyle("#979797", "#5484ED");
+ public static final BiomedAppointmentStyle BLUE = new BiomedAppointmentStyle("#979797", "#A4BDFC");
+ public static final BiomedAppointmentStyle TURQUOISE = new BiomedAppointmentStyle("#979797", "#46D6DB");
+ public static final BiomedAppointmentStyle GREEN = new BiomedAppointmentStyle("#979797", "#7AE7BF");
+ public static final BiomedAppointmentStyle BOLD_GREEN = new BiomedAppointmentStyle("#979797", "#51B749");
+ public static final BiomedAppointmentStyle YELLOW = new BiomedAppointmentStyle("#979797", "#FBD75B");
+ public static final BiomedAppointmentStyle ORANGE = new BiomedAppointmentStyle("#979797", "#FFB878");
+ public static final BiomedAppointmentStyle RED = new BiomedAppointmentStyle("#979797", "#FF887C");
+ public static final BiomedAppointmentStyle BOLD_RED = new BiomedAppointmentStyle("#979797", "#DC2127");
+ public static final BiomedAppointmentStyle PURPLE = new BiomedAppointmentStyle("#979797", "#DBADFF");
+ public static final BiomedAppointmentStyle GRAY = new BiomedAppointmentStyle("#979797", "#E1E1E1");
+
+ public static final List STYLES = new ArrayList();
+
+ static {
+ STYLES.add(DARK_GRAY);
+ STYLES.add(BOLD_BLUE);
+ STYLES.add(BLUE);
+ STYLES.add(TURQUOISE);
+ STYLES.add(GREEN);
+ STYLES.add(BOLD_GREEN);
+ STYLES.add(YELLOW);
+ STYLES.add(ORANGE);
+ STYLES.add(RED);
+ STYLES.add(BOLD_RED);
+ STYLES.add(PURPLE);
+ STYLES.add(GRAY);
+
+ }
+
+ private BiomedAppointmentTheme() {
+ }
+}
diff --git a/src/com/biomed/client/resources/BiomedDayViewStyleManager.java b/src/com/biomed/client/resources/BiomedDayViewStyleManager.java
new file mode 100644
index 0000000..d9fca2f
--- /dev/null
+++ b/src/com/biomed/client/resources/BiomedDayViewStyleManager.java
@@ -0,0 +1,18 @@
+package com.biomed.client.resources;
+
+import com.biomed.client.ui.schedule.BiomedAppointment;
+import com.bradrydzewski.gwt.calendar.client.Appointment;
+import com.bradrydzewski.gwt.calendar.client.ThemeAppointmentStyle;
+import com.bradrydzewski.gwt.calendar.client.dayview.DayViewStyleManager;
+
+public class BiomedDayViewStyleManager extends DayViewStyleManager {
+ @Override
+ protected ThemeAppointmentStyle getDefaultViewAppointmentStyleForTheme() {
+ return BiomedAppointmentTheme.GRAY;
+ }
+
+ @Override
+ protected ThemeAppointmentStyle getViewAppointmentStyleForTheme(Appointment appointment) {
+ return ((BiomedAppointment) appointment).getTheme();
+ }
+}
diff --git a/src/com/biomed/client/resources/BiomedMonthViewStyleManager.java b/src/com/biomed/client/resources/BiomedMonthViewStyleManager.java
new file mode 100644
index 0000000..10c57a0
--- /dev/null
+++ b/src/com/biomed/client/resources/BiomedMonthViewStyleManager.java
@@ -0,0 +1,19 @@
+package com.biomed.client.resources;
+
+import com.biomed.client.ui.schedule.BiomedAppointment;
+import com.bradrydzewski.gwt.calendar.client.Appointment;
+import com.bradrydzewski.gwt.calendar.client.ThemeAppointmentStyle;
+import com.bradrydzewski.gwt.calendar.client.monthview.MonthViewStyleManager;
+
+public class BiomedMonthViewStyleManager extends MonthViewStyleManager {
+
+ @Override
+ protected ThemeAppointmentStyle getDefaultViewAppointmentStyleForTheme() {
+ return BiomedAppointmentTheme.GRAY;
+ }
+
+ @Override
+ protected ThemeAppointmentStyle getViewAppointmentStyleForTheme(Appointment appointment) {
+ return ((BiomedAppointment) appointment).getTheme();
+ }
+}
diff --git a/src/com/biomed/client/resources/CellTableResource.css b/src/com/biomed/client/resources/CellTableResource.css
new file mode 100644
index 0000000..14ced96
--- /dev/null
+++ b/src/com/biomed/client/resources/CellTableResource.css
@@ -0,0 +1,96 @@
+.cellTableWidget {
+ width: 100%;
+}
+
+.cellTableFirstColumn {
+ border-left: none;
+}
+
+.cellTableLastColumn {
+
+}
+
+.cellTableFooter {
+}
+
+.cellTableHeader {
+ text-align: center; font-size: 11px; padding: 3px 5px 2px 5px; color: #909090; background: #eee;
+}
+
+.cellTableCell {
+ border-left: 1px solid #DFDFDF; box-shadow: 0 1px 0 #fafafa inset; -webkit-box-shadow: 0 1px 0 #fafafa inset; -moz-box-shadow: 0 1px 0 #fafafa inset;
+ padding: 7px 11px; vertical-align: middle;
+}
+
+.cellTableFirstColumnFooter {
+}
+
+.cellTableFirstColumnHeader {
+}
+
+.cellTableLastColumnFooter {
+
+}
+
+.cellTableLastColumnHeader {
+
+}
+
+.cellTableSortableHeader {
+}
+
+.cellTableSortableHeader:hover {
+}
+
+.cellTableSortedHeaderAscending {
+
+}
+
+.cellTableSortedHeaderDescending {
+
+}
+
+.cellTableEvenRow {
+ background: #f2f2f2;
+ border-top: 1px solid #DFDFDF;
+}
+
+.cellTableEvenRowCell {
+
+}
+
+.cellTableOddRow {
+ border-top: 1px solid #DFDFDF;
+}
+
+.cellTableOddRowCell {
+
+}
+
+.cellTableHoveredRow {
+ background: #ddd;
+ cursor: pointer;
+}
+
+.cellTableHoveredRowCell {
+
+}
+
+.cellTableKeyboardSelectedRow {
+}
+
+.cellTableKeyboardSelectedRowCell {
+
+}
+
+ .cellTableSelectedRow {
+}
+
+.cellTableSelectedRowCell {
+}
+
+.cellTableKeyboardSelectedCell {
+}
+
+.cellTableLoading {
+}
\ No newline at end of file
diff --git a/src/com/biomed/client/resources/CellTableResource.java b/src/com/biomed/client/resources/CellTableResource.java
new file mode 100644
index 0000000..93c8153
--- /dev/null
+++ b/src/com/biomed/client/resources/CellTableResource.java
@@ -0,0 +1,17 @@
+package com.biomed.client.resources;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.user.cellview.client.CellTable;
+import com.google.gwt.user.cellview.client.CellTable.Resources;
+
+public interface CellTableResource extends Resources {
+
+ public CellTable.Resources INSTANCE =
+ GWT.create(CellTableResource.class);
+
+ /**
+ * The styles used in this widget.
+ */
+ @Source("CellTableResource.css")
+ CellTable.Style cellTableStyle();
+}
\ No newline at end of file
diff --git a/src/com/biomed/client/services/BiomedService.java b/src/com/biomed/client/services/BiomedService.java
new file mode 100644
index 0000000..1033281
--- /dev/null
+++ b/src/com/biomed/client/services/BiomedService.java
@@ -0,0 +1,72 @@
+package com.biomed.client.services;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import com.biomed.client.ui.MainPanel;
+import com.biomed.shared.api.GetScheduleAction;
+import com.biomed.shared.api.GetTechListAction;
+import com.biomed.shared.api.SqlResultSet;
+import com.biomed.shared.dispatch.DispatchAction;
+import com.biomed.shared.dispatch.DispatchAsync;
+import com.biomed.shared.dispatch.DispatchResult;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+@Singleton
+public class BiomedService {
+
+ private static final String LOADING = "Loading...";
+ private static final String SAVING = "Saving...";
+
+ private final MainPanel mainPanel;
+ private final DispatchAsync dispatcher;
+
+ @Inject
+ public BiomedService(MainPanel mainPanel, DispatchAsync dispatcher) {
+ this.mainPanel = mainPanel;
+ this.dispatcher = dispatcher;
+ }
+
+ public , R extends DispatchResult> void execute(A action,
+ AsyncCallback callback) {
+ dispatcher.execute(action, createCallback(LOADING, callback));
+ }
+
+ public void getSchedule(String startDate, String endDate, int techId,
+ AsyncCallback callback) {
+ GetScheduleAction action = new GetScheduleAction();
+ action.startDate = startDate;
+ action.endDate = endDate;
+ action.techId = techId;
+
+ dispatcher.execute(action, createCallback(LOADING, callback));
+ }
+
+ public void getTechList(AsyncCallback callback) {
+ GetTechListAction action = new GetTechListAction();
+ dispatcher.execute(action, createCallback(LOADING, callback));
+ }
+
+ private AsyncCallback createCallback(final String message,
+ final AsyncCallback callback) {
+
+ if (message != null) {
+ mainPanel.showMessage(message);
+ }
+
+ return new AsyncCallback() {
+
+ @Override
+ public void onFailure(Throwable caught) {
+ callback.onFailure(caught);
+ }
+
+ @Override
+ public void onSuccess(T result) {
+ callback.onSuccess(result);
+ mainPanel.hideMessage();
+ }
+ };
+ }
+
+}
diff --git a/src/com/biomed/client/services/ServicesGinModule.java b/src/com/biomed/client/services/ServicesGinModule.java
new file mode 100644
index 0000000..e1355c9
--- /dev/null
+++ b/src/com/biomed/client/services/ServicesGinModule.java
@@ -0,0 +1,10 @@
+package com.biomed.client.services;
+
+import com.google.gwt.inject.client.AbstractGinModule;
+
+public class ServicesGinModule extends AbstractGinModule {
+
+ @Override
+ protected void configure() {
+ }
+}
diff --git a/src/com/biomed/client/ui/DashboardPanel.java b/src/com/biomed/client/ui/DashboardPanel.java
new file mode 100644
index 0000000..ad890ee
--- /dev/null
+++ b/src/com/biomed/client/ui/DashboardPanel.java
@@ -0,0 +1,12 @@
+package com.biomed.client.ui;
+
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Label;
+
+public class DashboardPanel extends Composite implements DashboardView {
+
+ public DashboardPanel() {
+ initWidget(new Label("DashboardPanel"));
+ }
+
+}
diff --git a/src/com/biomed/client/ui/DashboardView.java b/src/com/biomed/client/ui/DashboardView.java
new file mode 100644
index 0000000..0d5790b
--- /dev/null
+++ b/src/com/biomed/client/ui/DashboardView.java
@@ -0,0 +1,9 @@
+package com.biomed.client.ui;
+
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.inject.ImplementedBy;
+
+@ImplementedBy(DashboardPanel.class)
+public interface DashboardView extends IsWidget {
+
+}
diff --git a/src/com/biomed/client/ui/MainPanel.java b/src/com/biomed/client/ui/MainPanel.java
new file mode 100644
index 0000000..5cf02c5
--- /dev/null
+++ b/src/com/biomed/client/ui/MainPanel.java
@@ -0,0 +1,112 @@
+package com.biomed.client.ui;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import com.biomed.client.BiomedActivityManager;
+import com.biomed.client.BiomedActivityMapper;
+import com.biomed.client.BiomedSite;
+import com.biomed.client.components.NavigationPanel;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.AcceptsOneWidget;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.UIObject;
+import com.google.web.bindery.event.shared.EventBus;
+
+@Singleton
+public class MainPanel extends Composite {
+
+ interface Binder extends UiBinder {
+ }
+
+ private Logger logger = Logger.getLogger("");
+
+ @UiField(provided = true)
+ NavigationPanel navigationPanel;
+
+ @UiField
+ SimplePanel sidebar;
+ @UiField
+ SimplePanel content;
+ @UiField
+ DivElement sidebarContainer;
+
+ @UiField
+ DivElement loadingPanel;
+ @UiField
+ SpanElement message;
+
+ @UiField
+ DivElement errorPanel;
+
+ @Inject
+ public MainPanel(NavigationPanel navigationPanel, BiomedActivityMapper mapper, EventBus eventBus,
+ Binder binder) {
+
+ this.navigationPanel = navigationPanel;
+ initWidget(binder.createAndBindUi(this));
+
+ UIObject.setVisible(loadingPanel, false);
+ UIObject.setVisible(errorPanel, false);
+
+ BiomedActivityManager activityManager = new BiomedActivityManager(mapper, eventBus);
+ activityManager.setSiteDisplay(BiomedSite.CONTENT, content);
+ activityManager.setSiteDisplay(BiomedSite.SIDEBAR, new SidebarPanel());
+
+ GWT.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
+
+ @Override
+ public void onUncaughtException(Throwable e) {
+ hideMessage();
+ UIObject.setVisible(errorPanel, true);
+ logger.log(Level.SEVERE, "Unhandled Exception: " + e.getMessage(), e);
+ }
+ });
+ }
+
+ private class SidebarPanel implements AcceptsOneWidget {
+ @Override
+ public void setWidget(IsWidget w) {
+ boolean hasSidebar = w != null;
+
+ sidebar.setWidget(w);
+ UIObject.setVisible(sidebarContainer, hasSidebar);
+ Document.get().getDocumentElement().setClassName(hasSidebar ? "wide" : "thin");
+ }
+ }
+
+ public void beginSave() {
+ }
+
+ public void endSave() {
+ }
+
+ public void beginLoading() {
+ showMessage("Loading...");
+ }
+
+ public void endLoading() {
+ hideMessage();
+ }
+
+ public void showMessage(String message) {
+ this.message.setInnerText(message);
+ UIObject.setVisible(loadingPanel, true);
+ }
+
+ public void hideMessage() {
+ UIObject.setVisible(loadingPanel, false);
+ }
+}
diff --git a/src/com/biomed/client/ui/MainPanel.ui.xml b/src/com/biomed/client/ui/MainPanel.ui.xml
new file mode 100644
index 0000000..d169fff
--- /dev/null
+++ b/src/com/biomed/client/ui/MainPanel.ui.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+ An unexpected error has occurred, Please refresh your browser and try again.
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/com/biomed/client/ui/clienteditor/ClientDevicesEditorPanel.java b/src/com/biomed/client/ui/clienteditor/ClientDevicesEditorPanel.java
new file mode 100644
index 0000000..eec0dd5
--- /dev/null
+++ b/src/com/biomed/client/ui/clienteditor/ClientDevicesEditorPanel.java
@@ -0,0 +1,114 @@
+package com.biomed.client.ui.clienteditor;
+
+import javax.inject.Inject;
+
+import com.biomed.client.components.Pager;
+import com.biomed.client.resources.CellTableResource;
+import com.biomed.shared.api.dto.DeviceDTO;
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.cellview.client.CellTable;
+import com.google.gwt.user.cellview.client.TextColumn;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.view.client.HasData;
+
+public class ClientDevicesEditorPanel extends Composite {
+
+ interface Binder extends UiBinder {
+ }
+
+ private static final DateTimeFormat format = DateTimeFormat.getShortDateFormat();
+
+ @UiField(provided = true) CellTable table;
+ @UiField(provided = true) Pager pager;
+
+ @Inject
+ public ClientDevicesEditorPanel(Binder binder) {
+ buildTable();
+ buildPager();
+
+ initWidget(binder.createAndBindUi(this));
+ }
+
+ public HasData getTable() {
+ return table;
+ }
+
+ private void buildTable() {
+ table = new CellTable(20, CellTableResource.INSTANCE);
+ table.setWidth("100%", true);
+
+ TextColumn identifierColumn = new TextColumn() {
+ @Override
+ public String getValue(DeviceDTO device) {
+ return device.getIdentifier();
+ }
+ };
+
+ TextColumn nameColumn = new TextColumn() {
+ @Override
+ public String getValue(DeviceDTO device) {
+ return device.getName();
+ }
+ };
+
+ TextColumn manufacturerColumn = new TextColumn() {
+ @Override
+ public String getValue(DeviceDTO device) {
+ return device.getManufacturer();
+ }
+ };
+
+ TextColumn modelColumn = new TextColumn() {
+ @Override
+ public String getValue(DeviceDTO device) {
+ return device.getModel();
+ }
+ };
+
+ TextColumn serialColumn = new TextColumn() {
+ @Override
+ public String getValue(DeviceDTO device) {
+ return device.getSerial();
+ }
+ };
+
+ TextColumn statusColumn = new TextColumn() {
+ @Override
+ public String getValue(DeviceDTO device) {
+ return device.getStatus();
+ }
+ };
+
+ TextColumn lastPmColumn = new TextColumn() {
+ @Override
+ public String getValue(DeviceDTO device) {
+ if (device.getLastPm() != null) {
+ return format.format(device.getLastPm());
+ } else {
+ return null;
+ }
+ }
+ };
+
+ table.addColumn(identifierColumn, "Device ID");
+ table.addColumn(nameColumn, "Device");
+ table.addColumn(manufacturerColumn, "Manufacturer");
+ table.addColumn(modelColumn, "Model #");
+ table.addColumn(serialColumn, "Serial #");
+ table.addColumn(statusColumn, "Status");
+ table.addColumn(lastPmColumn, "Last Pm");
+ }
+
+ private void buildPager() {
+ pager = new Pager();
+ pager.setDisplay(table);
+ pager.setPageSize(20);
+ }
+
+ public void showLoading() {
+ pager.startLoading();
+ }
+}
\ No newline at end of file
diff --git a/src/com/biomed/client/ui/clienteditor/ClientDevicesEditorPanel.ui.xml b/src/com/biomed/client/ui/clienteditor/ClientDevicesEditorPanel.ui.xml
new file mode 100644
index 0000000..c9d2d08
--- /dev/null
+++ b/src/com/biomed/client/ui/clienteditor/ClientDevicesEditorPanel.ui.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/com/biomed/client/ui/clienteditor/ClientEditorSidebarPanel.java b/src/com/biomed/client/ui/clienteditor/ClientEditorSidebarPanel.java
new file mode 100644
index 0000000..07b5110
--- /dev/null
+++ b/src/com/biomed/client/ui/clienteditor/ClientEditorSidebarPanel.java
@@ -0,0 +1,55 @@
+package com.biomed.client.ui.clienteditor;
+
+import javax.inject.Inject;
+
+import com.github.gwtbootstrap.client.ui.NavLink;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+
+public class ClientEditorSidebarPanel extends Composite {
+
+ interface Binder extends UiBinder { }
+
+ @UiField NavLink overviewTab;
+ @UiField NavLink frequencyTab;
+ @UiField NavLink devicesTab;
+ @UiField NavLink workordersTab;
+
+ @Inject
+ public ClientEditorSidebarPanel(Binder binder) {
+ initWidget(binder.createAndBindUi(this));
+ }
+
+ public void setActive(Tabs tab) {
+ overviewTab.setActive(tab == Tabs.OVERVIEW);
+ frequencyTab.setActive(tab == Tabs.FREQUENCY);
+ devicesTab.setActive(tab == Tabs.DEVICES);
+ workordersTab.setActive(tab == Tabs.WORKORDERS);
+ }
+
+ public HasClickHandlers getOverviewTab() {
+ return overviewTab;
+ }
+
+ public HasClickHandlers getFrequencyTab() {
+ return frequencyTab;
+ }
+
+ public HasClickHandlers getDevicesTab() {
+ return devicesTab;
+ }
+
+ public HasClickHandlers getWorkordersTab() {
+ return workordersTab;
+ }
+
+ public enum Tabs {
+ OVERVIEW,
+ FREQUENCY,
+ DEVICES,
+ WORKORDERS
+ }
+}
diff --git a/src/com/biomed/client/ui/clienteditor/ClientEditorSidebarPanel.ui.xml b/src/com/biomed/client/ui/clienteditor/ClientEditorSidebarPanel.ui.xml
new file mode 100644
index 0000000..35d6e92
--- /dev/null
+++ b/src/com/biomed/client/ui/clienteditor/ClientEditorSidebarPanel.ui.xml
@@ -0,0 +1,12 @@
+
+
+
+
+ Overview
+ Frequency
+ Devices
+ Workorders
+
+
+
\ No newline at end of file
diff --git a/src/com/biomed/client/ui/clienteditor/ClientFrequencyEditorPanel.java b/src/com/biomed/client/ui/clienteditor/ClientFrequencyEditorPanel.java
new file mode 100644
index 0000000..d8787fd
--- /dev/null
+++ b/src/com/biomed/client/ui/clienteditor/ClientFrequencyEditorPanel.java
@@ -0,0 +1,128 @@
+package com.biomed.client.ui.clienteditor;
+
+import javax.inject.Inject;
+
+import com.biomed.shared.api.dto.FrequencyDTO;
+import com.biomed.shared.api.dto.FrequencyType;
+import com.biomed.shared.api.dto.Month;
+import com.github.gwtbootstrap.client.ui.Button;
+import com.github.gwtbootstrap.client.ui.constants.ButtonType;
+import com.google.gwt.dom.builder.shared.HtmlBuilderFactory;
+import com.google.gwt.dom.builder.shared.TableBuilder;
+import com.google.gwt.dom.builder.shared.TableRowBuilder;
+import com.google.gwt.dom.builder.shared.TableSectionBuilder;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NodeList;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.EventListener;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+
+public class ClientFrequencyEditorPanel extends Composite {
+
+ interface Binder extends UiBinder {
+ }
+
+ @UiField
+ HTMLPanel rootPanel;
+
+ Button saveButton;
+
+ FrequencyDTO frequencies;
+
+ @Inject
+ public ClientFrequencyEditorPanel(Binder binder) {
+ saveButton = new Button("Save");
+ saveButton.setType(ButtonType.PRIMARY);
+ initWidget(binder.createAndBindUi(this));
+ }
+
+ public void setFrequencies(FrequencyDTO frequencies) {
+ this.frequencies = frequencies;
+
+ buildTable();
+ }
+
+ public FrequencyDTO getFrequencies() {
+ return frequencies;
+ }
+
+ public HasClickHandlers getSaveButton() {
+ return saveButton;
+ }
+
+ private void buildTable() {
+
+ HtmlBuilderFactory builder = HtmlBuilderFactory.get();
+
+ TableBuilder table = builder.createTableBuilder();
+ table.className("table table-bordered frequency");
+ TableSectionBuilder thead = table.startTHead();
+ TableRowBuilder theadRow = thead.startTR();
+ theadRow.startTH().end();
+ theadRow.startTH().text("JAN").end();
+ theadRow.startTH().text("FEB").end();
+ theadRow.startTH().text("MAR").end();
+ theadRow.startTH().text("APR").end();
+ theadRow.startTH().text("MAY").end();
+ theadRow.startTH().text("JUN").end();
+ theadRow.startTH().text("JUL").end();
+ theadRow.startTH().text("AUG").end();
+ theadRow.startTH().text("SEP").end();
+ theadRow.startTH().text("OCT").end();
+ theadRow.startTH().text("NOV").end();
+ theadRow.startTH().text("DEC").end();
+
+ theadRow.end();
+ thead.end();
+
+ TableSectionBuilder tbody = table.startTBody();
+ for (FrequencyType type : FrequencyType.values()) {
+ TableRowBuilder tr = tbody.startTR();
+
+ tr.startTD().className("name").text(type.getLabel()).end();
+
+ for (Month month : Month.values()) {
+ tr.startTD()
+ .className("item " + (frequencies.getFrequency(type, month) ? "enabled" : "disabled"))
+ .attribute("data-month", month.toString()).attribute("data-frequency", type.toString())
+ .html(SafeHtmlUtils.fromSafeConstant(" ")).end();
+ }
+ tr.end();
+ }
+
+ TableRowBuilder tr = tbody.startTR();
+ tr.startTD().colSpan(13).className("controls").id("controls").end();
+ tr.end();
+
+ tbody.end();
+ table.end();
+ rootPanel.getElement().appendChild(table.finish());
+ rootPanel.add(saveButton, "controls");
+
+ NodeList elements = rootPanel.getElement().getElementsByTagName("td");
+ for (int i = 0; i < elements.getLength(); i++) {
+ final com.google.gwt.user.client.Element cell = elements.getItem(i).cast();
+ if (cell.getClassName().contains("item")) {
+ DOM.setEventListener(cell, new EventListener() {
+ @Override
+ public void onBrowserEvent(Event event) {
+ Element target = event.getCurrentTarget();
+ FrequencyType type = FrequencyType.valueOf(target.getAttribute("data-frequency"));
+ Month month = Month.valueOf(target.getAttribute("data-month"));
+ boolean enabled = "enabled".equals(target.getClassName());
+ frequencies.setFrequency(type, month, !enabled);
+ target.setClassName(enabled ? "disabled" : "enabled");
+ }
+ });
+ DOM.sinkEvents(cell, Event.ONCLICK);
+ }
+ }
+ }
+
+}
diff --git a/src/com/biomed/client/ui/clienteditor/ClientFrequencyEditorPanel.ui.xml b/src/com/biomed/client/ui/clienteditor/ClientFrequencyEditorPanel.ui.xml
new file mode 100644
index 0000000..bcad58d
--- /dev/null
+++ b/src/com/biomed/client/ui/clienteditor/ClientFrequencyEditorPanel.ui.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/com/biomed/client/ui/clienteditor/ClientOverviewEditorPanel.java b/src/com/biomed/client/ui/clienteditor/ClientOverviewEditorPanel.java
new file mode 100644
index 0000000..5afa1b1
--- /dev/null
+++ b/src/com/biomed/client/ui/clienteditor/ClientOverviewEditorPanel.java
@@ -0,0 +1,160 @@
+package com.biomed.client.ui.clienteditor;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import com.biomed.shared.api.dto.State;
+import com.github.gwtbootstrap.client.ui.Button;
+import com.github.gwtbootstrap.client.ui.TextArea;
+import com.github.gwtbootstrap.client.ui.TextBox;
+import com.github.gwtbootstrap.client.ui.ValueListBox;
+import com.google.common.collect.Lists;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.text.shared.Renderer;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.HasValue;
+
+public class ClientOverviewEditorPanel extends Composite {
+
+ interface Binder extends UiBinder {
+ }
+
+ @UiField
+ TextBox identifierField;
+
+ @UiField
+ TextBox nameField;
+
+ @UiField
+ TextBox primaryAddressField;
+
+ @UiField
+ TextBox primaryAddress2Field;
+
+ @UiField
+ TextBox primaryCityField;
+
+ @UiField(provided = true)
+ ValueListBox primaryStateField;
+
+ @UiField
+ TextBox primaryZipField;
+
+ @UiField
+ TextBox primaryContactField;
+
+ @UiField
+ TextBox primaryPhoneField;
+
+ @UiField
+ TextBox primaryEmailField;
+
+ @UiField
+ TextBox secondaryContactField;
+
+ @UiField
+ TextBox secondaryPhoneField;
+
+ @UiField
+ TextBox secondaryEmailField;
+
+ @UiField
+ TextArea notesField;
+
+ @UiField
+ Button saveButton;
+
+ @Inject
+ public ClientOverviewEditorPanel(Binder binder) {
+ List values = Lists.newArrayList(State.values());
+
+ primaryStateField = new ValueListBox(new StateRenderer());
+ primaryStateField.setAcceptableValues(values);
+
+ initWidget(binder.createAndBindUi(this));
+ }
+
+ public HasValue getIdentifierField() {
+ return identifierField;
+ }
+
+ public HasValue getNameField() {
+ return nameField;
+ }
+
+ public HasValue getPrimaryAddressField() {
+ return primaryAddressField;
+ }
+
+ public HasValue getPrimaryAddress2Field() {
+ return primaryAddress2Field;
+ }
+
+ public HasValue getPrimaryCityField() {
+ return primaryCityField;
+ }
+
+ public HasValue getPrimaryStateField() {
+ return primaryStateField;
+ }
+
+ public HasValue getPrimaryZipField() {
+ return primaryZipField;
+ }
+
+ public HasValue getNotesField() {
+ return notesField;
+ }
+
+ public HasClickHandlers getSaveButton() {
+ return saveButton;
+ }
+
+ public HasValue getPrimaryPhoneField() {
+ return primaryPhoneField;
+ }
+
+ public HasValue getPrimaryEmailField() {
+ return primaryEmailField;
+ }
+
+ public HasValue getSecondaryPhoneField() {
+ return secondaryPhoneField;
+ }
+
+ public HasValue getSecondaryEmailField() {
+ return secondaryEmailField;
+ }
+
+ public HasValue getPrimaryContactField() {
+ return primaryContactField;
+ }
+
+ public HasValue getSecondaryContactField() {
+ return secondaryContactField;
+ }
+
+ private class StateRenderer implements Renderer {
+
+ @Override
+ public String render(State object) {
+ if (object == null) {
+ return "";
+ }
+
+ return object.toString();
+ }
+
+ @Override
+ public void render(State object, Appendable appendable) throws IOException {
+ if (object != null) {
+ appendable.append(object.toString());
+ }
+ }
+ }
+}
diff --git a/src/com/biomed/client/ui/clienteditor/ClientOverviewEditorPanel.ui.xml b/src/com/biomed/client/ui/clienteditor/ClientOverviewEditorPanel.ui.xml
new file mode 100644
index 0000000..495e6e4
--- /dev/null
+++ b/src/com/biomed/client/ui/clienteditor/ClientOverviewEditorPanel.ui.xml
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+ Client Identifier:
+
+
+
+
+
+ Client Name:
+
+
+
+
+
+
+ Primary Address
+
+ Address:
+
+
+
+
+
+ Address 2:
+
+
+
+
+
+ City:
+
+
+
+
+
+ State:
+
+
+
+
+
+ Zip:
+
+
+
+
+
+
+ Primary Contact Information
+
+ Contact:
+
+
+
+
+
+ Phone:
+
+
+
+
+
+ E-Mail:
+
+
+
+
+
+
+ Secondary Contact Information
+
+ Contact:
+
+
+
+
+
+ Phone:
+
+
+
+
+
+ E-Mail:
+
+
+
+
+
+
+ Notes
+
+ Notes:
+
+
+
+
+
+
+
+ Save
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/com/biomed/client/ui/clienteditor/ClientWorkordersEditorPanel.java b/src/com/biomed/client/ui/clienteditor/ClientWorkordersEditorPanel.java
new file mode 100644
index 0000000..54da2f4
--- /dev/null
+++ b/src/com/biomed/client/ui/clienteditor/ClientWorkordersEditorPanel.java
@@ -0,0 +1,122 @@
+package com.biomed.client.ui.clienteditor;
+
+import javax.inject.Inject;
+
+import com.biomed.client.activity.EditClientWorkordersActivity;
+import com.biomed.client.components.Pager;
+import com.biomed.client.resources.CellTableResource;
+import com.biomed.shared.api.dto.WorkorderDTO;
+import com.biomed.shared.api.dto.WorkorderStatusDTO;
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.cellview.client.CellTable;
+import com.google.gwt.user.cellview.client.TextColumn;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.view.client.CellPreviewEvent;
+import com.google.gwt.view.client.CellPreviewEvent.Handler;
+import com.google.gwt.view.client.HasData;
+
+public class ClientWorkordersEditorPanel extends Composite {
+
+ interface Binder extends UiBinder {
+ }
+
+ private static final DateTimeFormat format = DateTimeFormat.getShortDateFormat();
+
+ @UiField(provided = true) CellTable table;
+ @UiField(provided = true) Pager pager;
+
+ public EditClientWorkordersActivity activity;
+
+ @Inject
+ public ClientWorkordersEditorPanel(Binder binder) {
+ buildTable();
+ buildPager();
+
+ initWidget(binder.createAndBindUi(this));
+
+ }
+
+ public HasData getTable() {
+ return table;
+ }
+
+ private void buildTable() {
+ table = new CellTable(20, CellTableResource.INSTANCE);
+ table.setWidth("100%", true);
+
+ TextColumn technicianColumn = new TextColumn() {
+ @Override
+ public String getValue(WorkorderDTO workorder) {
+ return workorder.getTechFirstName() + " " + workorder.getTechLastName();
+ }
+ };
+
+ TextColumn jobDateColumn = new TextColumn() {
+ @Override
+ public String getValue(WorkorderDTO workorder) {
+ if (workorder.getJobDate() != null) {
+ return format.format(workorder.getJobDate());
+ } else {
+ return null;
+ }
+ }
+ };
+
+ TextColumn remarksColumn = new TextColumn() {
+ @Override
+ public String getValue(WorkorderDTO workorder) {
+ return workorder.getRemarks();
+ }
+ };
+
+ TextColumn reasonColumn = new TextColumn() {
+ @Override
+ public String getValue(WorkorderDTO workorder) {
+ return workorder.getReason();
+ }
+ };
+
+ TextColumn jobStatusColumn = new TextColumn() {
+ @Override
+ public String getValue(WorkorderDTO workorder) {
+ WorkorderStatusDTO status = WorkorderStatusDTO.getById(workorder.getJobStatusId());
+ if (status != null) {
+ return status.getLabel();
+ } else {
+ return "(Unknown)";
+ }
+ }
+ };
+
+ table.addColumn(technicianColumn, "Technician");
+ table.addColumn(jobDateColumn, "Job Date");
+ table.addColumn(remarksColumn, "Remarks");
+ table.addColumn(reasonColumn, "Reason");
+ table.addColumn(jobStatusColumn, "Job Status");
+
+ table.addCellPreviewHandler(new Handler() {
+
+ @Override
+ public void onCellPreview(CellPreviewEvent event) {
+ boolean isClick = "click".equals(event.getNativeEvent().getType());
+
+ if (isClick) {
+ activity.onWorkorderClicked(event.getValue().getWorkorderId());
+ }
+ }
+ });
+ }
+
+ private void buildPager() {
+ pager = new Pager();
+ pager.setDisplay(table);
+ pager.setPageSize(20);
+ }
+
+ public void showLoading() {
+ pager.startLoading();
+ }
+}
\ No newline at end of file
diff --git a/src/com/biomed/client/ui/clienteditor/ClientWorkordersEditorPanel.ui.xml b/src/com/biomed/client/ui/clienteditor/ClientWorkordersEditorPanel.ui.xml
new file mode 100644
index 0000000..c9d2d08
--- /dev/null
+++ b/src/com/biomed/client/ui/clienteditor/ClientWorkordersEditorPanel.ui.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/com/biomed/client/ui/clientlist/ClientListPanel.java b/src/com/biomed/client/ui/clientlist/ClientListPanel.java
new file mode 100644
index 0000000..5288d05
--- /dev/null
+++ b/src/com/biomed/client/ui/clientlist/ClientListPanel.java
@@ -0,0 +1,109 @@
+package com.biomed.client.ui.clientlist;
+
+import javax.inject.Inject;
+
+import com.biomed.client.activity.ViewClientsActivity;
+import com.biomed.client.components.Pager;
+import com.biomed.client.resources.CellTableResource;
+import com.biomed.shared.api.dto.ClientDTO;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.cellview.client.CellTable;
+import com.google.gwt.user.cellview.client.TextColumn;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.view.client.CellPreviewEvent;
+import com.google.gwt.view.client.CellPreviewEvent.Handler;
+import com.google.gwt.view.client.HasData;
+
+public class ClientListPanel extends Composite {
+
+ interface Binder extends UiBinder {
+ }
+
+ @UiField(provided = true) CellTable table;
+ @UiField(provided = true) Pager pager;
+
+ public ViewClientsActivity activity;
+
+ @Inject
+ public ClientListPanel(Binder binder) {
+ buildTable();
+ buildPager();
+
+ initWidget(binder.createAndBindUi(this));
+ }
+
+ public HasData getTable() {
+ return table;
+ }
+
+ private void buildTable() {
+ table = new CellTable(20, CellTableResource.INSTANCE);
+ table.setWidth("100%", true);
+
+ TextColumn idColumn = new TextColumn() {
+ @Override
+ public String getValue(ClientDTO client) {
+ return client.getIdentifier();
+ }
+ };
+ idColumn.setSortable(true);
+ table.addColumn(idColumn, "ID");
+ table.setColumnWidth(idColumn, 30.0, Unit.PCT);
+
+ TextColumn nameColumn = new TextColumn() {
+ @Override
+ public String getValue(ClientDTO client) {
+ return client.getName();
+ }
+ };
+ nameColumn.setSortable(true);
+ table.addColumn(nameColumn, "Client Name");
+ table.setColumnWidth(nameColumn, 30.0, Unit.PCT);
+
+
+ TextColumn contactColumn = new TextColumn() {
+ @Override
+ public String getValue(ClientDTO client) {
+ return client.getPrimaryContactName();
+ }
+ };
+ contactColumn.setSortable(true);
+ table.addColumn(contactColumn, "Contact");
+ table.setColumnWidth(contactColumn, 30.0, Unit.PCT);
+
+ TextColumn phoneColumn = new TextColumn() {
+ @Override
+ public String getValue(ClientDTO client) {
+ return client.getPrimaryPhone();
+ }
+ };
+ phoneColumn.setSortable(true);
+ table.addColumn(phoneColumn, "Phone");
+ table.setColumnWidth(phoneColumn, 15.0, Unit.PCT);
+
+ table.addCellPreviewHandler(new Handler() {
+
+ @Override
+ public void onCellPreview(CellPreviewEvent event) {
+ boolean isClick = "click".equals(event.getNativeEvent().getType());
+
+ if (isClick) {
+ activity.onClientClicked(event.getValue().getId());
+ }
+ }
+ });
+ }
+
+ private void buildPager() {
+ pager = new Pager();
+ pager.setDisplay(table);
+ pager.setPageSize(20);
+ }
+
+ public void showLoading() {
+ pager.startLoading();
+ }
+}
diff --git a/src/com/biomed/client/ui/clientlist/ClientListPanel.ui.xml b/src/com/biomed/client/ui/clientlist/ClientListPanel.ui.xml
new file mode 100644
index 0000000..c9d2d08
--- /dev/null
+++ b/src/com/biomed/client/ui/clientlist/ClientListPanel.ui.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/com/biomed/client/ui/clientlist/ClientListSidebarPanel.java b/src/com/biomed/client/ui/clientlist/ClientListSidebarPanel.java
new file mode 100644
index 0000000..c613db1
--- /dev/null
+++ b/src/com/biomed/client/ui/clientlist/ClientListSidebarPanel.java
@@ -0,0 +1,51 @@
+package com.biomed.client.ui.clientlist;
+
+import javax.inject.Inject;
+
+import com.biomed.client.activity.ViewClientsActivity;
+import com.github.gwtbootstrap.client.ui.TextBox;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+
+public class ClientListSidebarPanel extends Composite {
+
+ interface Binder extends UiBinder {}
+
+ public ViewClientsActivity activity;
+
+ @UiField Anchor addClient;
+ @UiField Anchor search;
+ @UiField TextBox searchBox;
+
+ @Inject
+ public ClientListSidebarPanel(Binder binder) {
+ initWidget(binder.createAndBindUi(this));
+
+ searchBox.addKeyPressHandler(new KeyPressHandler() {
+ @Override
+ public void onKeyPress(KeyPressEvent event) {
+ if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
+ activity.filterTable(searchBox.getText());
+ }
+ }
+ });
+ }
+
+ @UiHandler("addClient")
+ void onAddClientClicked(ClickEvent event) {
+ activity.onNewClientClicked();
+ }
+
+ @UiHandler("search")
+ void onSearchClicked(ClickEvent event) {
+ activity.filterTable(searchBox.getText());
+ }
+}
diff --git a/src/com/biomed/client/ui/clientlist/ClientListSidebarPanel.ui.xml b/src/com/biomed/client/ui/clientlist/ClientListSidebarPanel.ui.xml
new file mode 100644
index 0000000..67cde59
--- /dev/null
+++ b/src/com/biomed/client/ui/clientlist/ClientListSidebarPanel.ui.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+ Add Client
+
+
+
+
+
+
+ Search
+
+
+
\ No newline at end of file
diff --git a/src/com/biomed/client/ui/messages/MessageFor.java b/src/com/biomed/client/ui/messages/MessageFor.java
new file mode 100644
index 0000000..0271df0
--- /dev/null
+++ b/src/com/biomed/client/ui/messages/MessageFor.java
@@ -0,0 +1,39 @@
+package com.biomed.client.ui.messages;
+
+import com.google.gwt.text.shared.AbstractRenderer;
+
+public enum MessageFor {
+ ChrisEndres(10, "Chris Endres"),
+ RyanFoley(32, "Ryan Foley"),
+ AllisonHartlove(51, "Allison Hartlove"),
+ CarlEndres(11, "Carl Endres"),
+ WillTurner(54, "Will Turner"),
+ KelliKirk(59, "Kelli Kirk");
+
+ private final int id;
+ private final String label;
+
+ MessageFor(int id, String label) {
+ this.id = id;
+ this.label = label;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public static class Renderer extends AbstractRenderer {
+ @Override
+ public String render(MessageFor object) {
+ if (object == null) {
+ return "";
+ } else {
+ return object.getLabel();
+ }
+ }
+ }
+}
diff --git a/src/com/biomed/client/ui/messages/Messages.java b/src/com/biomed/client/ui/messages/Messages.java
new file mode 100644
index 0000000..ad163fb
--- /dev/null
+++ b/src/com/biomed/client/ui/messages/Messages.java
@@ -0,0 +1,12 @@
+package com.biomed.client.ui.messages;
+
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.inject.ImplementedBy;
+
+public interface Messages {
+
+ @ImplementedBy(MessagesPanel.class)
+ public interface View extends IsWidget {
+
+ }
+}
diff --git a/src/com/biomed/client/ui/messages/MessagesPanel.java b/src/com/biomed/client/ui/messages/MessagesPanel.java
new file mode 100644
index 0000000..3165cf7
--- /dev/null
+++ b/src/com/biomed/client/ui/messages/MessagesPanel.java
@@ -0,0 +1,118 @@
+package com.biomed.client.ui.messages;
+
+import javax.inject.Inject;
+
+import com.github.gwtbootstrap.client.ui.Button;
+import com.github.gwtbootstrap.client.ui.CheckBox;
+import com.github.gwtbootstrap.client.ui.TextBox;
+import com.github.gwtbootstrap.client.ui.ValueListBox;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.HasConstrainedValue;
+import com.google.gwt.user.client.ui.HasValue;
+
+public class MessagesPanel extends Composite implements Messages.View {
+
+ interface Binder extends UiBinder {
+ }
+
+ @UiField(provided = true)
+ ValueListBox messageForField;
+ @UiField
+ TextBox nameField;
+ @UiField
+ TextBox companyField;
+ @UiField
+ TextBox phoneField;
+ @UiField
+ TextBox phoneExtensionField;
+ @UiField
+ TextBox timeToReturnCallField;
+ @UiField
+ CheckBox telephonedField;
+ @UiField
+ CheckBox cameToSeeYouField;
+ @UiField
+ CheckBox wantsToSeeYouField;
+ @UiField
+ CheckBox returnedYourCallField;
+ @UiField
+ CheckBox pleaseCallField;
+ @UiField
+ CheckBox willCallAgainField;
+ @UiField
+ CheckBox rushField;
+ @UiField
+ CheckBox specialAttentionField;
+ @UiField
+ Button sendButton;
+
+ @Inject
+ public MessagesPanel(Binder binder) {
+ messageForField = new ValueListBox(new MessageFor.Renderer());
+ initWidget(binder.createAndBindUi(this));
+ }
+
+ public HasConstrainedValue getMessageForField() {
+ return messageForField;
+ }
+
+ public HasValue getNameField() {
+ return nameField;
+ }
+
+ public HasValue getCompanyField() {
+ return companyField;
+ }
+
+ public HasValue getPhoneField() {
+ return phoneField;
+ }
+
+ public HasValue getPhoneExtensionField() {
+ return phoneExtensionField;
+ }
+
+ public HasValue getTimeToReturnCallField() {
+ return timeToReturnCallField;
+ }
+
+ public HasValue getTelephonedField() {
+ return telephonedField;
+ }
+
+ public HasValue getCameToSeeYouField() {
+ return cameToSeeYouField;
+ }
+
+ public HasValue getWantsToSeeYouField() {
+ return wantsToSeeYouField;
+ }
+
+ public HasValue getReturnedYourCallField() {
+ return returnedYourCallField;
+ }
+
+ public HasValue getPleaseCallField() {
+ return pleaseCallField;
+ }
+
+ public HasValue getWillCallAgainField() {
+ return willCallAgainField;
+ }
+
+ public HasValue getRushField() {
+ return rushField;
+ }
+
+ public HasValue getSpecialAttentionField() {
+ return specialAttentionField;
+ }
+
+ public HasClickHandlers getSendButton() {
+ return sendButton;
+ }
+}
diff --git a/src/com/biomed/client/ui/messages/MessagesPanel.ui.xml b/src/com/biomed/client/ui/messages/MessagesPanel.ui.xml
new file mode 100644
index 0000000..3aceb0a
--- /dev/null
+++ b/src/com/biomed/client/ui/messages/MessagesPanel.ui.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+ Message For:
+
+
+
+
+
+ Name:
+
+
+
+
+
+ Company:
+
+
+
+
+
+ Phone:
+
+
+
+
+
+ Extension:
+
+
+
+
+
+ Time to return call:
+
+
+
+
+
+
+
+ Telephoned
+ Came to see you
+ Wants to see you
+ Returned your call
+ Please call
+ Will call again
+ Rush
+ Special Attention
+
+
+
+ Notes:
+
+
+
+
+
+ Send
+
+
+
+
+
\ No newline at end of file
diff --git a/src/com/biomed/client/ui/renderers/ClientDTORenderer.java b/src/com/biomed/client/ui/renderers/ClientDTORenderer.java
new file mode 100644
index 0000000..b0360c3
--- /dev/null
+++ b/src/com/biomed/client/ui/renderers/ClientDTORenderer.java
@@ -0,0 +1,22 @@
+package com.biomed.client.ui.renderers;
+
+import com.biomed.shared.api.dto.ClientDTO;
+import com.google.gwt.text.shared.AbstractRenderer;
+
+public class ClientDTORenderer extends AbstractRenderer {
+
+ private final String defaultValue;
+
+ public ClientDTORenderer(String defaultValue) {
+ this.defaultValue = defaultValue;
+ }
+
+ @Override
+ public String render(ClientDTO object) {
+ if (object != null) {
+ return object.getIdentifier();
+ } else {
+ return defaultValue;
+ }
+ }
+}
diff --git a/src/com/biomed/client/ui/renderers/ScheduleTimeRenderer.java b/src/com/biomed/client/ui/renderers/ScheduleTimeRenderer.java
new file mode 100644
index 0000000..7c9e5d1
--- /dev/null
+++ b/src/com/biomed/client/ui/renderers/ScheduleTimeRenderer.java
@@ -0,0 +1,22 @@
+package com.biomed.client.ui.renderers;
+
+import com.biomed.shared.api.dto.ScheduleTime;
+import com.google.gwt.text.shared.AbstractRenderer;
+
+public class ScheduleTimeRenderer extends AbstractRenderer {
+
+ private final String defaultValue;
+
+ public ScheduleTimeRenderer(String defaultValue) {
+ this.defaultValue = defaultValue;
+ }
+
+ @Override
+ public String render(ScheduleTime object) {
+ if (object != null) {
+ return object.getLabel();
+ } else {
+ return defaultValue;
+ }
+ }
+}
diff --git a/src/com/biomed/client/ui/renderers/UserDTORenderer.java b/src/com/biomed/client/ui/renderers/UserDTORenderer.java
new file mode 100644
index 0000000..042b3ca
--- /dev/null
+++ b/src/com/biomed/client/ui/renderers/UserDTORenderer.java
@@ -0,0 +1,22 @@
+package com.biomed.client.ui.renderers;
+
+import com.biomed.shared.api.dto.UserDTO;
+import com.google.gwt.text.shared.AbstractRenderer;
+
+public class UserDTORenderer extends AbstractRenderer {
+
+ private final String defaultValue;
+
+ public UserDTORenderer(String defaultValue) {
+ this.defaultValue = defaultValue;
+ }
+
+ @Override
+ public String render(UserDTO object) {
+ if (object != null) {
+ return object.getDisplayName();
+ } else {
+ return defaultValue;
+ }
+ }
+}
diff --git a/src/com/biomed/client/ui/renderers/WorkorderReasonDTORenderer.java b/src/com/biomed/client/ui/renderers/WorkorderReasonDTORenderer.java
new file mode 100644
index 0000000..4fd7159
--- /dev/null
+++ b/src/com/biomed/client/ui/renderers/WorkorderReasonDTORenderer.java
@@ -0,0 +1,22 @@
+package com.biomed.client.ui.renderers;
+
+import com.biomed.shared.api.dto.WorkorderReasonDTO;
+import com.google.gwt.text.shared.AbstractRenderer;
+
+public class WorkorderReasonDTORenderer extends AbstractRenderer {
+
+ private final String defaultValue;
+
+ public WorkorderReasonDTORenderer(String defaultValue) {
+ this.defaultValue = defaultValue;
+ }
+
+ @Override
+ public String render(WorkorderReasonDTO object) {
+ if (object != null) {
+ return object.getLabel();
+ } else {
+ return defaultValue;
+ }
+ }
+}
diff --git a/src/com/biomed/client/ui/renderers/WorkorderStatusDTORenderer.java b/src/com/biomed/client/ui/renderers/WorkorderStatusDTORenderer.java
new file mode 100644
index 0000000..9588ca3
--- /dev/null
+++ b/src/com/biomed/client/ui/renderers/WorkorderStatusDTORenderer.java
@@ -0,0 +1,22 @@
+package com.biomed.client.ui.renderers;
+
+import com.biomed.shared.api.dto.WorkorderStatusDTO;
+import com.google.gwt.text.shared.AbstractRenderer;
+
+public class WorkorderStatusDTORenderer extends AbstractRenderer {
+
+ private final String defaultValue;
+
+ public WorkorderStatusDTORenderer(String defaultValue) {
+ this.defaultValue = defaultValue;
+ }
+
+ @Override
+ public String render(WorkorderStatusDTO object) {
+ if (object != null) {
+ return object.getLabel();
+ } else {
+ return defaultValue;
+ }
+ }
+}
diff --git a/src/com/biomed/client/ui/schedule/BiomedAppointment.java b/src/com/biomed/client/ui/schedule/BiomedAppointment.java
new file mode 100644
index 0000000..5c3994a
--- /dev/null
+++ b/src/com/biomed/client/ui/schedule/BiomedAppointment.java
@@ -0,0 +1,16 @@
+package com.biomed.client.ui.schedule;
+
+import com.bradrydzewski.gwt.calendar.client.Appointment;
+import com.bradrydzewski.gwt.calendar.client.ThemeAppointmentStyle;
+
+public class BiomedAppointment extends Appointment {
+ private ThemeAppointmentStyle theme;
+
+ public ThemeAppointmentStyle getTheme() {
+ return theme;
+ }
+
+ public void setTheme(ThemeAppointmentStyle theme) {
+ this.theme = theme;
+ }
+}
diff --git a/src/com/biomed/client/ui/schedule/DateUtil.java b/src/com/biomed/client/ui/schedule/DateUtil.java
new file mode 100644
index 0000000..e57cc33
--- /dev/null
+++ b/src/com/biomed/client/ui/schedule/DateUtil.java
@@ -0,0 +1,31 @@
+package com.biomed.client.ui.schedule;
+
+import java.util.Date;
+
+public class DateUtil {
+
+ public static Date addDays(Date date, int days) {
+ return new Date(date.getTime() + (86400000 * days));
+ }
+
+ @SuppressWarnings("deprecation")
+ public static Date getWorkFirstDay(Date date) {
+ Date current = date;
+ while (current.getDay() != 1) {
+ current = new Date(current.getTime() - 86400000);
+ }
+
+ return current;
+ }
+
+ @SuppressWarnings("deprecation")
+ public static Date getNextWorkDay(Date date) {
+ Date current = date;
+
+ while (current.getDay() == 0 || current.getDay() == 6) {
+ current = new Date(current.getTime() + 86400000);
+ }
+
+ return current;
+ }
+}
diff --git a/src/com/biomed/client/ui/schedule/Schedule.java b/src/com/biomed/client/ui/schedule/Schedule.java
new file mode 100644
index 0000000..d4bcd6d
--- /dev/null
+++ b/src/com/biomed/client/ui/schedule/Schedule.java
@@ -0,0 +1,17 @@
+package com.biomed.client.ui.schedule;
+
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.inject.ImplementedBy;
+
+public interface Schedule {
+
+ @ImplementedBy(SchedulePanel.class)
+ public interface View extends IsWidget {
+
+ }
+
+ @ImplementedBy(ScheduleSidebarPanel.class)
+ public interface Sidebar extends IsWidget {
+
+ }
+}
diff --git a/src/com/biomed/client/ui/schedule/SchedulePanel.java b/src/com/biomed/client/ui/schedule/SchedulePanel.java
new file mode 100644
index 0000000..8805209
--- /dev/null
+++ b/src/com/biomed/client/ui/schedule/SchedulePanel.java
@@ -0,0 +1,181 @@
+package com.biomed.client.ui.schedule;
+
+import java.util.Date;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import com.biomed.client.resources.BiomedAppointmentTheme;
+import com.bradrydzewski.gwt.calendar.client.Appointment;
+import com.bradrydzewski.gwt.calendar.client.Calendar;
+import com.bradrydzewski.gwt.calendar.client.CalendarSettings;
+import com.bradrydzewski.gwt.calendar.client.CalendarViews;
+import com.google.common.collect.Lists;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.event.logical.shared.ResizeEvent;
+import com.google.gwt.event.logical.shared.ResizeHandler;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.maps.gwt.client.Geocoder;
+import com.google.maps.gwt.client.Geocoder.Callback;
+import com.google.maps.gwt.client.GeocoderRequest;
+import com.google.maps.gwt.client.GeocoderResult;
+import com.google.maps.gwt.client.GeocoderStatus;
+import com.google.maps.gwt.client.GoogleMap;
+import com.google.maps.gwt.client.LatLng;
+import com.google.maps.gwt.client.MapOptions;
+import com.google.maps.gwt.client.MapTypeId;
+import com.google.maps.gwt.client.Marker;
+import com.google.maps.gwt.client.MarkerOptions;
+
+public class SchedulePanel extends Composite implements Schedule.View {
+
+ interface Binder extends UiBinder {
+ }
+
+ @UiField
+ HTMLPanel rootPanel;
+ @UiField
+ FlowPanel calContainer;
+ @UiField
+ FlowPanel mapContainer;
+
+ private static final LatLng center = LatLng.create(39.257147, -76.685686);
+
+ private Calendar calendar;
+ private GoogleMap map;
+ private Geocoder geocoder;
+
+ private List markers = Lists.newArrayList();
+ private List appointments = Lists.newArrayList();
+
+ @Inject
+ public SchedulePanel(Binder binder) {
+ initWidget(binder.createAndBindUi(this));
+
+ buildCalendar();
+ buildMap();
+
+ Window.addResizeHandler(new ResizeHandler() {
+ @Override
+ public void onResize(ResizeEvent event) {
+ handleResize(event.getWidth(), event.getHeight());
+ }
+ });
+ }
+
+ @Override
+ protected void onLoad() {
+ super.onLoad();
+
+ handleResize(Window.getClientWidth(), Window.getClientHeight());
+ }
+
+ private void handleResize(int width, int height) {
+ width = width - 312;
+ height = height - 49;
+
+ mapContainer.setWidth(width + "px");
+ mapContainer.setHeight((height * 0.40) + "px");
+ calContainer.setWidth(width + "px");
+ calContainer.setHeight((height * 0.60) + "px");
+
+ if (calendar != null) {
+ calendar.doSizing();
+ }
+
+ map.triggerResize();
+ map.panTo(center);
+ }
+
+ private void buildCalendar() {
+ CalendarSettings settings = new CalendarSettings();
+ settings.setEnableDragDrop(false);
+ settings.setEnableDragDropCreation(false);
+
+ calendar = new Calendar();
+ calendar.setSettings(settings);
+ calendar.setView(CalendarViews.DAY);
+ calendar.setWidth("100%");
+ calendar.setHeight("100%");
+ calContainer.add(calendar);
+ }
+
+ public void addWorkorders(List workorders) {
+ calendar.clearAppointments();
+ appointments.clear();
+ calendar.suspendLayout();
+
+ for (Marker m : markers) {
+ m.setMap((GoogleMap) null);
+ }
+ markers.clear();
+
+ for (Workorder w : workorders) {
+ String address = null;
+ if (w.clientAddress != null) {
+ address = w.clientAddress + "\n" + w.clientCity + ", " + w.clientState + ". " + w.clientZip;
+ }
+
+ String description = "" + w.clientName + " (" + w.clientIdentifier + ") ";
+ if (address != null) {
+ description += " " + address;
+ }
+ BiomedAppointment apt = new BiomedAppointment();
+ apt.setTitle(w.techName);
+ apt.setDescription(description);
+ apt.setLocation(address);
+ apt.setStart(w.jobStart);
+ apt.setEnd(w.jobEnd);
+ apt.setTheme(BiomedAppointmentTheme.STYLES.get(w.color));
+
+ calendar.addAppointment(apt);
+ appointments.add(apt);
+
+ if (address != null) {
+ GeocoderRequest request = GeocoderRequest.create();
+ request.setAddress(address);
+ geocoder.geocode(request, new Callback() {
+ @Override
+ public void handle(JsArray a, GeocoderStatus b) {
+ if (b == GeocoderStatus.OK) {
+ LatLng location = a.get(0).getGeometry().getLocation();
+ MarkerOptions options = MarkerOptions.create();
+ options.setPosition(location);
+ options.setMap(map);
+ markers.add(Marker.create(options));
+ }
+ }
+ });
+ }
+ }
+
+ calendar.resumeLayout();
+ calendar.doLayout();
+ }
+
+ public void setView(Date date, int days) {
+ calendar.setDate(date, days);
+ }
+
+ private void buildMap() {
+ MapOptions myOptions = MapOptions.create();
+ myOptions.setZoom(8.0);
+ myOptions.setCenter(center);
+ myOptions.setMapTypeId(MapTypeId.ROADMAP);
+ mapContainer.setWidth("500px");
+ mapContainer.setHeight("500px");
+ map = GoogleMap.create(mapContainer.getElement(), myOptions);
+
+ MarkerOptions options = MarkerOptions.create();
+ options.setPosition(center);
+ options.setMap(map);
+ Marker.create(options);
+
+ geocoder = Geocoder.create();
+ }
+}
diff --git a/src/com/biomed/client/ui/schedule/SchedulePanel.ui.xml b/src/com/biomed/client/ui/schedule/SchedulePanel.ui.xml
new file mode 100644
index 0000000..1445011
--- /dev/null
+++ b/src/com/biomed/client/ui/schedule/SchedulePanel.ui.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/com/biomed/client/ui/schedule/ScheduleSidebarPanel.java b/src/com/biomed/client/ui/schedule/ScheduleSidebarPanel.java
new file mode 100644
index 0000000..ec3908f
--- /dev/null
+++ b/src/com/biomed/client/ui/schedule/ScheduleSidebarPanel.java
@@ -0,0 +1,287 @@
+package com.biomed.client.ui.schedule;
+
+import java.util.Date;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import com.biomed.client.activity.ViewScheduleActivity;
+import com.biomed.client.ui.renderers.ClientDTORenderer;
+import com.biomed.client.ui.renderers.ScheduleTimeRenderer;
+import com.biomed.client.ui.renderers.UserDTORenderer;
+import com.biomed.client.ui.renderers.WorkorderReasonDTORenderer;
+import com.biomed.client.ui.renderers.WorkorderStatusDTORenderer;
+import com.biomed.shared.api.dto.ClientDTO;
+import com.biomed.shared.api.dto.ScheduleTime;
+import com.biomed.shared.api.dto.UserDTO;
+import com.biomed.shared.api.dto.WorkorderReasonDTO;
+import com.biomed.shared.api.dto.WorkorderStatusDTO;
+import com.github.gwtbootstrap.client.ui.Button;
+import com.github.gwtbootstrap.client.ui.TextBox;
+import com.github.gwtbootstrap.client.ui.ValueListBox;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.LIElement;
+import com.google.gwt.dom.client.NodeList;
+import com.google.gwt.dom.client.UListElement;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.google.gwt.safehtml.client.SafeHtmlTemplates;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.EventListener;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.HasConstrainedValue;
+import com.google.gwt.user.client.ui.HasValue;
+import com.google.gwt.user.datepicker.client.DateBox;
+import com.google.gwt.user.datepicker.client.DateBox.Format;
+import com.google.gwt.user.datepicker.client.DatePicker;
+
+public class ScheduleSidebarPanel extends Composite implements Schedule.Sidebar {
+
+ public interface Templates extends SafeHtmlTemplates {
+
+ @Template("{0} ")
+ SafeHtml techItem(String name, int techId);
+ }
+
+ interface Binder extends UiBinder {
+ }
+
+ @UiField
+ LIElement scheduleItem;
+ @UiField
+ LIElement techsItem;
+ @UiField
+ LIElement reportsItem;
+ @UiField
+ Anchor techsButton;
+ @UiField
+ Anchor scheduleButton;
+
+ @UiField
+ HTMLPanel techs;
+ @UiField
+ HTMLPanel schedule;
+ @UiField
+ HTMLPanel reports;
+ @UiField
+ UListElement userListPanel;
+ @UiField
+ DatePicker datePicker;
+
+ @UiField(provided = true)
+ ValueListBox startTimePicker;
+
+ @UiField(provided = true)
+ ValueListBox endTimePicker;
+
+ @UiField(provided = true)
+ ValueListBox techPicker;
+
+ @UiField(provided = true)
+ ValueListBox reportTechPicker;
+
+ @UiField(provided = true)
+ ValueListBox clientPicker;
+
+ @UiField(provided = true)
+ ValueListBox workorderStatusField;
+
+ @UiField(provided = true)
+ ValueListBox reasonPicker;
+
+ @UiField
+ Button saveButton;
+
+ @UiField
+ Button sendReport;
+
+ @UiField DateBox jobDateField;
+ @UiField DateBox reportDateField;
+ @UiField TextBox requestedByField;
+ @UiField TextBox remarksField;
+
+
+ private final Templates templates;
+
+ public ViewScheduleActivity activity;
+
+ @Inject
+ public ScheduleSidebarPanel(Binder binder, Templates templates) {
+ this.templates = templates;
+
+ startTimePicker = new ValueListBox(new ScheduleTimeRenderer(null));
+ endTimePicker = new ValueListBox(new ScheduleTimeRenderer(null));
+ techPicker = new ValueListBox(new UserDTORenderer("Please select ..."));
+ reportTechPicker = new ValueListBox(new UserDTORenderer("Please select ..."));
+ clientPicker = new ValueListBox(new ClientDTORenderer("Please select ..."));
+ workorderStatusField = new ValueListBox(new WorkorderStatusDTORenderer(null));
+ reasonPicker = new ValueListBox(new WorkorderReasonDTORenderer(null));
+
+ initWidget(binder.createAndBindUi(this));
+ schedule.setVisible(false);
+ reports.setVisible(false);
+
+ Format format = new DateBox.DefaultFormat(DateTimeFormat.getShortDateFormat());
+ jobDateField.setFormat(format);
+ jobDateField.setValue(new Date());
+
+ reportDateField.setFormat(format);
+ reportDateField.setValue(new Date());
+
+ datePicker.setValue(new Date(), false);
+ }
+
+ public void setTechList(final List techs) {
+
+ techPicker.setAcceptableValues(techs);
+ reportTechPicker.setAcceptableValues(techs);
+
+ SafeHtmlBuilder builder = new SafeHtmlBuilder();
+ builder.append(templates.techItem("- All Techs -", 0));
+
+ for (final UserDTO t : techs) {
+ builder.append(templates.techItem(t.getDisplayName(), t.getId()));
+ }
+ userListPanel.setInnerSafeHtml(builder.toSafeHtml());
+
+ NodeList elements = userListPanel.getElementsByTagName("a");
+ for (int i = 0; i < elements.getLength(); i++) {
+ final com.google.gwt.user.client.Element anchor = elements.getItem(i).cast();
+
+ DOM.setEventListener(anchor, new EventListener() {
+ @Override
+ public void onBrowserEvent(Event event) {
+ Element target = event.getCurrentTarget();
+
+ int techId = Integer.parseInt(target.getAttribute("data"));
+ activity.setTechId(techId);
+
+ if (techId == 0) {
+ techPicker.setValue(null);
+ }
+ for (UserDTO tech : techs) {
+ if (tech.getId() == techId) {
+ techPicker.setValue(tech);
+ break;
+ }
+ }
+ }
+ });
+ DOM.sinkEvents(anchor, Event.ONCLICK);
+ }
+ }
+
+ @UiHandler("datePicker")
+ void onDatePickerValueChanged(ValueChangeEvent event) {
+ activity.setDate(event.getValue());
+ jobDateField.setValue(event.getValue(), false);
+ }
+
+ @UiHandler("jobDateField")
+ void onJobDateFieldValueChanged(ValueChangeEvent event) {
+ activity.setDate(event.getValue());
+ datePicker.setValue(event.getValue(), false);
+ }
+
+ @UiHandler("techsButton")
+ void onTechsButtonClicked(ClickEvent event) {
+ techsItem.setClassName("clicked");
+ scheduleItem.setClassName("");
+ reportsItem.setClassName("");
+ techs.setVisible(true);
+ schedule.setVisible(false);
+ reports.setVisible(false);
+ }
+
+ @UiHandler("scheduleButton")
+ void onScheduleButtonClicked(ClickEvent event) {
+ techsItem.setClassName("");
+ scheduleItem.setClassName("clicked");
+ reportsItem.setClassName("");
+ techs.setVisible(false);
+ schedule.setVisible(true);
+ reports.setVisible(false);
+ }
+
+ @UiHandler("reportButton")
+ void onReportButtonClicked(ClickEvent event) {
+ techsItem.setClassName("");
+ scheduleItem.setClassName("");
+ reportsItem.setClassName("clicked");
+ techs.setVisible(false);
+ schedule.setVisible(false);
+ reports.setVisible(true);
+ }
+
+ @UiHandler("techPicker")
+ void onTechPickerValueChanged(ValueChangeEvent event) {
+ if (event.getValue() != null) {
+ activity.setTechId(event.getValue().getId());
+ } else {
+ activity.setTechId(0);
+ }
+ }
+
+ @UiHandler("sendReport")
+ void onSendReportButtonClicked(ClickEvent event) {
+ activity.sendReport();
+ }
+
+ public HasValue getJobDateField() {
+ return jobDateField;
+ }
+
+ public HasValue getReportDateField() {
+ return reportDateField;
+ }
+
+ public HasConstrainedValue getStartTimeField() {
+ return startTimePicker;
+ }
+
+ public HasConstrainedValue getEndTimeField() {
+ return endTimePicker;
+ }
+
+ public HasConstrainedValue getClientField() {
+ return clientPicker;
+ }
+
+ public HasConstrainedValue getTechField() {
+ return techPicker;
+ }
+
+ public HasConstrainedValue getReportTechField() {
+ return reportTechPicker;
+ }
+
+ public HasConstrainedValue getWorkorderStatusField() {
+ return workorderStatusField;
+ }
+
+ public HasConstrainedValue getReasonField() {
+ return reasonPicker;
+ }
+
+ public HasValue getRequestedByField() {
+ return requestedByField;
+ }
+
+ public HasValue getRemarksField() {
+ return remarksField;
+ }
+
+ public HasClickHandlers getSaveButton() {
+ return saveButton;
+ }
+}
diff --git a/src/com/biomed/client/ui/schedule/ScheduleSidebarPanel.ui.xml b/src/com/biomed/client/ui/schedule/ScheduleSidebarPanel.ui.xml
new file mode 100644
index 0000000..a86b088
--- /dev/null
+++ b/src/com/biomed/client/ui/schedule/ScheduleSidebarPanel.ui.xml
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Job Date:
+
+
+
+ Client:
+
+
+
+ Technician:
+
+
+
+ Start Time:
+
+
+
+ End Time:
+
+
+
+ Reason:
+
+
+
+ Requested By:
+
+
+
+ Remarks:
+
+
+
+ Status:
+
+
+
+ Schedule
+
+
+
+
+ Date:
+
+
+
+ Technician:
+
+
+
+ Send Report
+
+
+
+
\ No newline at end of file
diff --git a/src/com/biomed/client/ui/schedule/Workorder.java b/src/com/biomed/client/ui/schedule/Workorder.java
new file mode 100644
index 0000000..d1fc728
--- /dev/null
+++ b/src/com/biomed/client/ui/schedule/Workorder.java
@@ -0,0 +1,52 @@
+package com.biomed.client.ui.schedule;
+
+import java.util.Date;
+
+import com.biomed.shared.api.PropertyBag;
+import com.google.gwt.i18n.shared.DateTimeFormat;
+
+
+public class Workorder {
+ public int id;
+ public Date jobDate;
+ public Date jobStart;
+ public Date jobEnd;
+ public int clientId;
+ public String clientIdentifier;
+ public String clientName;
+ public String clientAddress;
+ public String clientAddress2;
+ public String clientCity;
+ public String clientState;
+ public String clientZip;
+ public int techId;
+ public String techName;
+ public int color;
+
+ public Workorder() { }
+
+ public Workorder(PropertyBag bag) {
+ DateTimeFormat format = DateTimeFormat.getFormat("yyyy-MM-dd");
+
+ id = bag.getInteger("workorder_id");
+
+ jobDate = format.parse(bag.getString("job_date"));
+
+ int start = bag.getInteger("job_start");
+ int end = bag.getInteger("job_end");
+
+ jobStart = new Date(jobDate.getTime() + (((start / 100 * 60) + (start % 100)) * 60000));
+ jobEnd = new Date(jobDate.getTime() + (((end / 100 * 60) + (end % 100)) * 60000));
+
+ clientId = bag.getInteger("client_id");
+ clientIdentifier = bag.getString("client_identification");
+ clientName = bag.getString("client_name");
+ clientAddress = bag.getString("client_address");
+ clientAddress2 = bag.getString("client_address_2");
+ clientCity = bag.getString("client_city");
+ clientState = bag.getString("client_state");
+ clientZip = bag.getString("client_zip");
+ techId = bag.getInteger("tech_id");
+ techName = bag.getString("tech_first_name") + " " + bag.getString("tech_last_name");
+ }
+}
diff --git a/src/com/biomed/client/ui/workorders/EditWorkorderPanel.java b/src/com/biomed/client/ui/workorders/EditWorkorderPanel.java
new file mode 100644
index 0000000..55601a7
--- /dev/null
+++ b/src/com/biomed/client/ui/workorders/EditWorkorderPanel.java
@@ -0,0 +1,167 @@
+package com.biomed.client.ui.workorders;
+
+import java.util.Date;
+
+import javax.inject.Inject;
+
+import com.biomed.client.ui.renderers.ScheduleTimeRenderer;
+import com.biomed.client.ui.renderers.UserDTORenderer;
+import com.biomed.client.ui.renderers.WorkorderReasonDTORenderer;
+import com.biomed.client.ui.renderers.WorkorderStatusDTORenderer;
+import com.biomed.shared.api.dto.EditWorkorderViewDTO;
+import com.biomed.shared.api.dto.ScheduleTime;
+import com.biomed.shared.api.dto.UserDTO;
+import com.biomed.shared.api.dto.WorkorderReasonDTO;
+import com.biomed.shared.api.dto.WorkorderStatusDTO;
+import com.github.gwtbootstrap.client.ui.Button;
+import com.github.gwtbootstrap.client.ui.TextArea;
+import com.github.gwtbootstrap.client.ui.ValueListBox;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.HasConstrainedValue;
+import com.google.gwt.user.client.ui.HasValue;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.datepicker.client.DateBox;
+import com.google.gwt.user.datepicker.client.DateBox.Format;
+
+public class EditWorkorderPanel extends Composite {
+ interface Binder extends UiBinder {
+ }
+
+ private static final DateTimeFormat format = DateTimeFormat
+ .getFormat(PredefinedFormat.DATE_MEDIUM);
+
+ @UiField
+ Button saveButton;
+ @UiField
+ Button deleteButton;
+ @UiField
+ Label workorderId;
+ @UiField
+ Label workorderScheduledDate;
+ @UiField
+ Label clientName;
+ @UiField
+ Label clientAddress;
+ @UiField
+ Label clientAddress2;
+ @UiField
+ Label clientCity;
+ @UiField
+ Label clientState;
+ @UiField
+ Label clientZip;
+ @UiField
+ Label clientPhone;
+ @UiField
+ Label clientPrimaryContact;
+ @UiField
+ Label workorderStartTime;
+ @UiField
+ Label workorderEndTime;
+ @UiField
+ Label workorderTechName;
+ @UiField(provided = true)
+ ValueListBox startTimePicker;
+ @UiField(provided = true)
+ ValueListBox endTimePicker;
+ @UiField(provided = true)
+ ValueListBox techPicker;
+ @UiField(provided = true)
+ ValueListBox workorderReasonField;
+ @UiField(provided = true)
+ ValueListBox workorderStatusField;
+ @UiField
+ TextArea actionTakenField;
+ @UiField
+ TextArea remarksField;
+ @UiField
+ DateBox workorderJobDateField;
+
+ @Inject
+ public EditWorkorderPanel(Binder binder) {
+ startTimePicker = new ValueListBox(new ScheduleTimeRenderer(null));
+ endTimePicker = new ValueListBox(new ScheduleTimeRenderer(null));
+ techPicker = new ValueListBox(new UserDTORenderer(null));
+
+ workorderReasonField = new ValueListBox(
+ new WorkorderReasonDTORenderer(null));
+ workorderReasonField.setValue(WorkorderReasonDTO.ADD_NEW_EQUIPMENT);
+ workorderReasonField.setAcceptableValues(WorkorderReasonDTO.VALUES);
+
+ workorderStatusField = new ValueListBox(
+ new WorkorderStatusDTORenderer(null));
+ workorderStatusField.setValue(WorkorderStatusDTO.COMPLETE);
+ workorderStatusField.setAcceptableValues(WorkorderStatusDTO.VALUES);
+
+ initWidget(binder.createAndBindUi(this));
+
+ Format format = new DateBox.DefaultFormat(DateTimeFormat.getShortDateFormat());
+ workorderJobDateField.setFormat(format);
+ workorderJobDateField.setValue(new Date());
+ }
+
+ public void setStaticValues(EditWorkorderViewDTO values) {
+ workorderId.setText(Integer.toString(values.getWorkorderId()));
+ if (values.getWorkorderScheduledDate() != null) {
+ workorderScheduledDate.setText(format.format(values.getWorkorderScheduledDate()));
+ }
+ clientName.setText(values.getClientName());
+ clientAddress.setText(values.getClientAddress());
+ clientAddress2.setText(values.getClientAddress2());
+ clientCity.setText(values.getClientCity());
+ clientState.setText(values.getClientState());
+ clientZip.setText(values.getClientZip());
+ clientPhone.setText(values.getClientPhone());
+ clientPrimaryContact.setText(values.getClientPrimaryContact());
+ workorderReasonField.setValue(values.getWorkorderReason());
+ workorderStatusField.setValue(values.getWorkorderStatus());
+ actionTakenField.setValue(values.getActionTaken());
+ remarksField.setValue(values.getRemarks());
+ }
+
+ public HasClickHandlers getSaveButton() {
+ return saveButton;
+ }
+
+ public HasClickHandlers getDeleteButton() {
+ return deleteButton;
+ }
+
+ public HasConstrainedValue getWorkorderReasonField() {
+ return workorderReasonField;
+ }
+
+ public HasConstrainedValue getWorkorderStatusField() {
+ return workorderStatusField;
+ }
+
+ public HasValue getActionTakenField() {
+ return actionTakenField;
+ }
+
+ public HasValue getRemarksField() {
+ return remarksField;
+ }
+
+ public HasConstrainedValue getStartTimeField() {
+ return startTimePicker;
+ }
+
+ public HasConstrainedValue getEndTimeField() {
+ return endTimePicker;
+ }
+
+ public HasConstrainedValue getTechField() {
+ return techPicker;
+ }
+
+ public HasValue getWorkorderJobDateField() {
+ return workorderJobDateField;
+ }
+}
diff --git a/src/com/biomed/client/ui/workorders/EditWorkorderPanel.ui.xml b/src/com/biomed/client/ui/workorders/EditWorkorderPanel.ui.xml
new file mode 100644
index 0000000..25194ea
--- /dev/null
+++ b/src/com/biomed/client/ui/workorders/EditWorkorderPanel.ui.xml
@@ -0,0 +1,97 @@
+
+
+
+
+
+
Workorder
+
+ Workorder #
+
+
+
+
+
+
+
+
+
+ , .
+
+ Phone:
+
+
+
+ Attn:
+
+
+
+
+
Schedule
+
+ Job scheduled date:
+
+
+
+ From:
+ to
+
+
+ Tech:
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/com/biomed/client/ui/workorders/Workorders.java b/src/com/biomed/client/ui/workorders/Workorders.java
new file mode 100644
index 0000000..67c595e
--- /dev/null
+++ b/src/com/biomed/client/ui/workorders/Workorders.java
@@ -0,0 +1,10 @@
+package com.biomed.client.ui.workorders;
+
+
+public interface Workorders {
+ public interface Presenter {
+ void onWorkorderClicked();
+ void filterTable(String filter);
+ }
+
+}
diff --git a/src/com/biomed/client/ui/workorders/WorkordersPanel.java b/src/com/biomed/client/ui/workorders/WorkordersPanel.java
new file mode 100644
index 0000000..ab8cf38
--- /dev/null
+++ b/src/com/biomed/client/ui/workorders/WorkordersPanel.java
@@ -0,0 +1,130 @@
+package com.biomed.client.ui.workorders;
+
+import javax.inject.Inject;
+
+import com.biomed.client.activity.ViewWorkordersActivity;
+import com.biomed.client.components.Pager;
+import com.biomed.client.resources.CellTableResource;
+import com.biomed.shared.api.dto.WorkorderDTO;
+import com.biomed.shared.api.dto.WorkorderStatusDTO;
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.cellview.client.CellTable;
+import com.google.gwt.user.cellview.client.TextColumn;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.view.client.CellPreviewEvent;
+import com.google.gwt.view.client.CellPreviewEvent.Handler;
+import com.google.gwt.view.client.HasData;
+
+public class WorkordersPanel extends Composite {
+
+ interface Binder extends UiBinder {
+ }
+
+ private static final DateTimeFormat format = DateTimeFormat.getShortDateFormat();
+
+ @UiField(provided = true) CellTable table;
+ @UiField(provided = true) Pager pager;
+
+ public ViewWorkordersActivity activity;
+
+ @Inject
+ public WorkordersPanel(Binder binder) {
+ buildTable();
+ buildPager();
+
+ initWidget(binder.createAndBindUi(this));
+
+ }
+
+ public HasData getTable() {
+ return table;
+ }
+
+ private void buildTable() {
+ table = new CellTable(20, CellTableResource.INSTANCE);
+ table.setWidth("100%", true);
+
+ TextColumn clientIdColumn = new TextColumn() {
+ @Override
+ public String getValue(WorkorderDTO workorder) {
+ return workorder.getClientIdentifier();
+ }
+ };
+
+ TextColumn clientNameColumn = new TextColumn() {
+ @Override
+ public String getValue(WorkorderDTO workorder) {
+ return workorder.getClientName();
+ }
+ };
+
+ TextColumn technicianColumn = new TextColumn() {
+ @Override
+ public String getValue(WorkorderDTO workorder) {
+ return workorder.getTechFirstName() + " " + workorder.getTechLastName();
+ }
+ };
+
+ TextColumn