voom-presenters 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +54 -0
- data/.ruby-version +1 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +104 -0
- data/LICENSE +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +45 -0
- data/ROADMAP.md +21 -0
- data/Rakefile +6 -0
- data/app/demo/component_status.pom +76 -0
- data/app/demo/components.pom +19 -0
- data/app/demo/components/badges.pom +25 -0
- data/app/demo/components/buttons.pom +51 -0
- data/app/demo/components/cards.pom +79 -0
- data/app/demo/components/chips.pom +91 -0
- data/app/demo/components/dialogs.pom +27 -0
- data/app/demo/components/drawers.pom +34 -0
- data/app/demo/components/expansion_panels.pom +21 -0
- data/app/demo/components/fabs-mini.pom +18 -0
- data/app/demo/components/fabs.pom +16 -0
- data/app/demo/components/footers.pom +36 -0
- data/app/demo/components/forms.pom +30 -0
- data/app/demo/components/headers.pom +37 -0
- data/app/demo/components/hidden_fields.pom +20 -0
- data/app/demo/components/icons.pom +94 -0
- data/app/demo/components/layouts.pom +44 -0
- data/app/demo/components/lists.pom +124 -0
- data/app/demo/components/menus.pom +43 -0
- data/app/demo/components/nav/drawer.pom +5 -0
- data/app/demo/components/nav/menu.pom +15 -0
- data/app/demo/components/selects.pom +30 -0
- data/app/demo/components/snackbar.pom +24 -0
- data/app/demo/components/snackbar_attached.pom +6 -0
- data/app/demo/components/tables.pom +39 -0
- data/app/demo/components/text_areas.pom +19 -0
- data/app/demo/components/text_fields.pom +57 -0
- data/app/demo/components/toggles.pom +29 -0
- data/app/demo/components/tooltips.pom +120 -0
- data/app/demo/event/actions.rb +86 -0
- data/app/demo/event/actions/dialog/show_dialog.pom +9 -0
- data/app/demo/event/actions/dialog/trigger.pom +86 -0
- data/app/demo/event/actions/nav/drawer.pom +5 -0
- data/app/demo/event/actions/nav/menu.pom +19 -0
- data/app/demo/event/autocomplete.pom +27 -0
- data/app/demo/event/field_level.pom +22 -0
- data/app/demo/event/form_level.pom +26 -0
- data/app/demo/event/nav/drawer.pom +5 -0
- data/app/demo/event/nav/menu.pom +14 -0
- data/app/demo/event/new_text.pom +6 -0
- data/app/demo/events.pom +98 -0
- data/app/demo/helpers/indented_grid.rb +14 -0
- data/app/demo/index.pom +19 -0
- data/app/demo/markdown.pom +73 -0
- data/app/demo/nav/top_nav.pom +42 -0
- data/app/demo/shared/code.pom +20 -0
- data/app/demo/shared/context_list.pom +29 -0
- data/app/demo/shared/debug.pom +17 -0
- data/app/demo/styles.pom +26 -0
- data/bin/console +22 -0
- data/bin/setup +8 -0
- data/component-status.yml +219 -0
- data/config.ru +21 -0
- data/lib/voom-presenters.rb +9 -0
- data/lib/voom/container_methods.rb +40 -0
- data/lib/voom/logger_methods.rb +11 -0
- data/lib/voom/parameters.rb +73 -0
- data/lib/voom/presenters-engine.rb +40 -0
- data/lib/voom/presenters.rb +13 -0
- data/lib/voom/presenters/api/app.rb +53 -0
- data/lib/voom/presenters/api/router.rb +94 -0
- data/lib/voom/presenters/app.rb +55 -0
- data/lib/voom/presenters/container_item.rb +16 -0
- data/lib/voom/presenters/demo/echo.rb +29 -0
- data/lib/voom/presenters/demo/search-terms.yml +50 -0
- data/lib/voom/presenters/demo/search.rb +29 -0
- data/lib/voom/presenters/dsl.rb +60 -0
- data/lib/voom/presenters/dsl/components/action.rb +35 -0
- data/lib/voom/presenters/dsl/components/avatar.rb +27 -0
- data/lib/voom/presenters/dsl/components/badge.rb +21 -0
- data/lib/voom/presenters/dsl/components/base.rb +78 -0
- data/lib/voom/presenters/dsl/components/button.rb +49 -0
- data/lib/voom/presenters/dsl/components/card.rb +119 -0
- data/lib/voom/presenters/dsl/components/checkbox.rb +16 -0
- data/lib/voom/presenters/dsl/components/chip.rb +48 -0
- data/lib/voom/presenters/dsl/components/content.rb +33 -0
- data/lib/voom/presenters/dsl/components/date_time.rb +17 -0
- data/lib/voom/presenters/dsl/components/dialog.rb +50 -0
- data/lib/voom/presenters/dsl/components/drawer.rb +40 -0
- data/lib/voom/presenters/dsl/components/event.rb +101 -0
- data/lib/voom/presenters/dsl/components/event_base.rb +20 -0
- data/lib/voom/presenters/dsl/components/expansion_panel.rb +46 -0
- data/lib/voom/presenters/dsl/components/footer.rb +25 -0
- data/lib/voom/presenters/dsl/components/form.rb +42 -0
- data/lib/voom/presenters/dsl/components/grid.rb +64 -0
- data/lib/voom/presenters/dsl/components/header.rb +33 -0
- data/lib/voom/presenters/dsl/components/hidden_field.rb +25 -0
- data/lib/voom/presenters/dsl/components/icon.rb +21 -0
- data/lib/voom/presenters/dsl/components/icon_base.rb +24 -0
- data/lib/voom/presenters/dsl/components/icon_toggle.rb +21 -0
- data/lib/voom/presenters/dsl/components/image.rb +36 -0
- data/lib/voom/presenters/dsl/components/input.rb +19 -0
- data/lib/voom/presenters/dsl/components/list.rb +39 -0
- data/lib/voom/presenters/dsl/components/lists/action.rb +72 -0
- data/lib/voom/presenters/dsl/components/lists/line.rb +83 -0
- data/lib/voom/presenters/dsl/components/lists/separator.rb +16 -0
- data/lib/voom/presenters/dsl/components/menu.rb +66 -0
- data/lib/voom/presenters/dsl/components/mixins/append.rb +20 -0
- data/lib/voom/presenters/dsl/components/mixins/attaches.rb +18 -0
- data/lib/voom/presenters/dsl/components/mixins/avatar.rb +18 -0
- data/lib/voom/presenters/dsl/components/mixins/buttons.rb +15 -0
- data/lib/voom/presenters/dsl/components/mixins/chips.rb +21 -0
- data/lib/voom/presenters/dsl/components/mixins/common.rb +50 -0
- data/lib/voom/presenters/dsl/components/mixins/content.rb +15 -0
- data/lib/voom/presenters/dsl/components/mixins/dialogs.rb +19 -0
- data/lib/voom/presenters/dsl/components/mixins/event.rb +19 -0
- data/lib/voom/presenters/dsl/components/mixins/expansion_panels.rb +15 -0
- data/lib/voom/presenters/dsl/components/mixins/grids.rb +15 -0
- data/lib/voom/presenters/dsl/components/mixins/helpers.rb +20 -0
- data/lib/voom/presenters/dsl/components/mixins/icons.rb +17 -0
- data/lib/voom/presenters/dsl/components/mixins/images.rb +15 -0
- data/lib/voom/presenters/dsl/components/mixins/menus.rb +15 -0
- data/lib/voom/presenters/dsl/components/mixins/selects.rb +17 -0
- data/lib/voom/presenters/dsl/components/mixins/snackbars.rb +18 -0
- data/lib/voom/presenters/dsl/components/mixins/text_fields.rb +35 -0
- data/lib/voom/presenters/dsl/components/mixins/toggles.rb +40 -0
- data/lib/voom/presenters/dsl/components/mixins/tooltips.rb +18 -0
- data/lib/voom/presenters/dsl/components/mixins/typography.rb +37 -0
- data/lib/voom/presenters/dsl/components/page.rb +29 -0
- data/lib/voom/presenters/dsl/components/radio_button.rb +14 -0
- data/lib/voom/presenters/dsl/components/select.rb +64 -0
- data/lib/voom/presenters/dsl/components/snackbar.rb +32 -0
- data/lib/voom/presenters/dsl/components/switch.rb +14 -0
- data/lib/voom/presenters/dsl/components/table.rb +102 -0
- data/lib/voom/presenters/dsl/components/text_area.rb +20 -0
- data/lib/voom/presenters/dsl/components/text_field.rb +74 -0
- data/lib/voom/presenters/dsl/components/toggle_base.rb +26 -0
- data/lib/voom/presenters/dsl/components/tooltip.rb +25 -0
- data/lib/voom/presenters/dsl/components/typography.rb +25 -0
- data/lib/voom/presenters/dsl/definer.rb +13 -0
- data/lib/voom/presenters/dsl/definition.rb +31 -0
- data/lib/voom/presenters/dsl/invalid_presenter.rb +8 -0
- data/lib/voom/presenters/dsl/lockable.rb +15 -0
- data/lib/voom/presenters/dsl/user_interface.rb +135 -0
- data/lib/voom/presenters/errors/parameter_validation.rb +10 -0
- data/lib/voom/presenters/errors/unprocessable.rb +8 -0
- data/lib/voom/presenters/helpers.rb +18 -0
- data/lib/voom/presenters/helpers/currency.rb +14 -0
- data/lib/voom/presenters/helpers/date.rb +22 -0
- data/lib/voom/presenters/helpers/errors.rb +11 -0
- data/lib/voom/presenters/helpers/inflector.rb +16 -0
- data/lib/voom/presenters/helpers/rails.rb +60 -0
- data/lib/voom/presenters/helpers/route.rb +11 -0
- data/lib/voom/presenters/helpers/time.rb +27 -0
- data/lib/voom/presenters/settings.rb +35 -0
- data/lib/voom/presenters/version.rb +5 -0
- data/lib/voom/presenters/web_client/app.rb +128 -0
- data/lib/voom/presenters/web_client/markdown_render.rb +16 -0
- data/lib/voom/presenters/web_client/router.rb +96 -0
- data/lib/voom/serializer.rb +43 -0
- data/lib/voom/symbol/to_str.rb +29 -0
- data/lib/voom/trace.rb +19 -0
- data/presenters.gemspec +37 -0
- data/public/.gitignore +2 -0
- data/public/bundle.css +11413 -0
- data/public/bundle.js +16456 -0
- data/public/dialog-polyfill.js +738 -0
- data/public/favicon.ico +0 -0
- data/public/img/demo/dog.png +0 -0
- data/public/img/demo/image_card.jpg +0 -0
- data/public/img/demo/rx.png +0 -0
- data/public/img/demo/welcome_card.jpg +0 -0
- data/public/img/settings/blue.png +0 -0
- data/public/img/settings/green.png +0 -0
- data/public/img/settings/orange.png +0 -0
- data/public/img/settings/purple.png +0 -0
- data/public/img/settings/red.png +0 -0
- data/public/img/settings/teal.png +0 -0
- data/public/img/settings/white.png +0 -0
- data/public/img/settings/yellow.png +0 -0
- data/public/scripts.js +289 -0
- data/public/style-bundle.js +73 -0
- data/public/styles.css +16 -0
- data/views/mdc/.gitignore +1 -0
- data/views/mdc/assets/js/app.js +10 -0
- data/views/mdc/assets/js/components/base-component.js +5 -0
- data/views/mdc/assets/js/components/button.js +15 -0
- data/views/mdc/assets/js/components/cards.js +3 -0
- data/views/mdc/assets/js/components/checkboxes.js +15 -0
- data/views/mdc/assets/js/components/chips.js +12 -0
- data/views/mdc/assets/js/components/date-time.js +6 -0
- data/views/mdc/assets/js/components/dialogs.js +32 -0
- data/views/mdc/assets/js/components/events.js +151 -0
- data/views/mdc/assets/js/components/events/autocomplete.js +96 -0
- data/views/mdc/assets/js/components/events/base.js +41 -0
- data/views/mdc/assets/js/components/events/dialog.js +25 -0
- data/views/mdc/assets/js/components/events/errors.js +142 -0
- data/views/mdc/assets/js/components/events/loads.js +22 -0
- data/views/mdc/assets/js/components/events/navigates.js +17 -0
- data/views/mdc/assets/js/components/events/posts.js +99 -0
- data/views/mdc/assets/js/components/events/replaces.js +82 -0
- data/views/mdc/assets/js/components/events/selects.js +28 -0
- data/views/mdc/assets/js/components/events/snackbar.js +23 -0
- data/views/mdc/assets/js/components/events/toggle_visiblity.js +19 -0
- data/views/mdc/assets/js/components/forms.js +57 -0
- data/views/mdc/assets/js/components/icon-toggles.js +21 -0
- data/views/mdc/assets/js/components/initialize.js +34 -0
- data/views/mdc/assets/js/components/lists.js +4 -0
- data/views/mdc/assets/js/components/menus.js +31 -0
- data/views/mdc/assets/js/components/mixins/event-handler.js +13 -0
- data/views/mdc/assets/js/components/selects.js +45 -0
- data/views/mdc/assets/js/components/snackbar.js +32 -0
- data/views/mdc/assets/js/components/text-fields.js +77 -0
- data/views/mdc/assets/js/dialog-polyfill.js +738 -0
- data/views/mdc/assets/js/material.js +3996 -0
- data/views/mdc/assets/js/utils/urls.js +54 -0
- data/views/mdc/assets/scss/app.scss +31 -0
- data/views/mdc/assets/scss/components/avatar.scss +41 -0
- data/views/mdc/assets/scss/components/button.scss +47 -0
- data/views/mdc/assets/scss/components/card.scss +54 -0
- data/views/mdc/assets/scss/components/checkbox.scss +5 -0
- data/views/mdc/assets/scss/components/chip.scss +30 -0
- data/views/mdc/assets/scss/components/datetime.scss +0 -0
- data/views/mdc/assets/scss/components/dialog.scss +3 -0
- data/views/mdc/assets/scss/components/expansion-panel.scss +153 -0
- data/views/mdc/assets/scss/components/fab.scss +8 -0
- data/views/mdc/assets/scss/components/grid.scss +10 -0
- data/views/mdc/assets/scss/components/icon-toggles.scss +9 -0
- data/views/mdc/assets/scss/components/icon.scss +34 -0
- data/views/mdc/assets/scss/components/image.scss +24 -0
- data/views/mdc/assets/scss/components/list.scss +9 -0
- data/views/mdc/assets/scss/components/menu.scss +17 -0
- data/views/mdc/assets/scss/components/select.scss +16 -0
- data/views/mdc/assets/scss/components/snackbar.scss +5 -0
- data/views/mdc/assets/scss/components/switch.scss +6 -0
- data/views/mdc/assets/scss/components/table-pagination.scss +65 -0
- data/views/mdc/assets/scss/components/textfield.scss +1 -0
- data/views/mdc/assets/scss/components/typography.scss +25 -0
- data/views/mdc/assets/scss/material.blue_grey-orange.min.css +8 -0
- data/views/mdc/assets/scss/styles.scss +11 -0
- data/views/mdc/assets/scss/theme.scss +5 -0
- data/views/mdc/body/drawer.erb +18 -0
- data/views/mdc/body/drawers/menu.erb +25 -0
- data/views/mdc/body/footer.erb +1 -0
- data/views/mdc/body/footers/large.erb +27 -0
- data/views/mdc/body/footers/menu_item.erb +6 -0
- data/views/mdc/body/footers/small.erb +14 -0
- data/views/mdc/body/header.erb +25 -0
- data/views/mdc/body/snackbar.erb +10 -0
- data/views/mdc/components/avatar.erb +24 -0
- data/views/mdc/components/badge.erb +2 -0
- data/views/mdc/components/body.erb +2 -0
- data/views/mdc/components/button.erb +17 -0
- data/views/mdc/components/buttons/button.erb +20 -0
- data/views/mdc/components/buttons/fab.erb +22 -0
- data/views/mdc/components/buttons/icon.erb +24 -0
- data/views/mdc/components/card.erb +49 -0
- data/views/mdc/components/checkbox.erb +22 -0
- data/views/mdc/components/chip.erb +31 -0
- data/views/mdc/components/content.erb +11 -0
- data/views/mdc/components/date_time.erb +30 -0
- data/views/mdc/components/dialog.erb +27 -0
- data/views/mdc/components/display.erb +2 -0
- data/views/mdc/components/event.erb +18 -0
- data/views/mdc/components/expansion_panel.erb +11 -0
- data/views/mdc/components/form.erb +15 -0
- data/views/mdc/components/grid.erb +24 -0
- data/views/mdc/components/headline.erb +2 -0
- data/views/mdc/components/hidden_field.erb +1 -0
- data/views/mdc/components/icon.erb +30 -0
- data/views/mdc/components/icon_toggle.erb +15 -0
- data/views/mdc/components/image.erb +7 -0
- data/views/mdc/components/link.erb +14 -0
- data/views/mdc/components/list.erb +14 -0
- data/views/mdc/components/list/actions.erb +6 -0
- data/views/mdc/components/list/actions/button.erb +1 -0
- data/views/mdc/components/list/actions/checkbox.erb +1 -0
- data/views/mdc/components/list/actions/icon.erb +1 -0
- data/views/mdc/components/list/actions/icon_toggle.erb +1 -0
- data/views/mdc/components/list/actions/radio_button.erb +1 -0
- data/views/mdc/components/list/actions/switch.erb +1 -0
- data/views/mdc/components/list/avatar.erb +5 -0
- data/views/mdc/components/list/checkbox.erb +5 -0
- data/views/mdc/components/list/icon.erb +5 -0
- data/views/mdc/components/list/info.erb +1 -0
- data/views/mdc/components/list/line.erb +34 -0
- data/views/mdc/components/list/menu.erb +23 -0
- data/views/mdc/components/list/separator.erb +1 -0
- data/views/mdc/components/menu.erb +29 -0
- data/views/mdc/components/modal.erb +15 -0
- data/views/mdc/components/radio_button.erb +11 -0
- data/views/mdc/components/render.erb +4 -0
- data/views/mdc/components/select.erb +22 -0
- data/views/mdc/components/snackbar.erb +17 -0
- data/views/mdc/components/static.erb +7 -0
- data/views/mdc/components/subheading.erb +2 -0
- data/views/mdc/components/switch.erb +13 -0
- data/views/mdc/components/table.erb +13 -0
- data/views/mdc/components/table/header.erb +7 -0
- data/views/mdc/components/table/pagination.erb +24 -0
- data/views/mdc/components/table/row.erb +14 -0
- data/views/mdc/components/text_area.erb +8 -0
- data/views/mdc/components/text_field.erb +27 -0
- data/views/mdc/components/title.erb +4 -0
- data/views/mdc/components/tooltip.erb +5 -0
- data/views/mdc/components/typography.erb +13 -0
- data/views/mdc/init-depends.sh +2 -0
- data/views/mdc/layout.erb +50 -0
- data/views/mdc/package-lock.json +11524 -0
- data/views/mdc/package.json +39 -0
- data/views/mdc/web.erb +1 -0
- data/views/mdc/webpack.config.js +47 -0
- metadata +539 -0
@@ -0,0 +1,13 @@
|
|
1
|
+
export let eventHandlerMixin = Base => class extends Base {
|
2
|
+
// idempotent event handling initialization
|
3
|
+
initEventListener(eventName, eventHandler) {
|
4
|
+
if (typeof this.eventsHandler === 'undefined') {
|
5
|
+
this.eventsHandler = {};
|
6
|
+
}
|
7
|
+
if (!this.eventsHandler[eventName]) {
|
8
|
+
// Delegate to the component if possible
|
9
|
+
this.eventsHandler[eventName] = eventHandler;
|
10
|
+
this.element.addEventListener(eventName, eventHandler);
|
11
|
+
}
|
12
|
+
}
|
13
|
+
};
|
@@ -0,0 +1,45 @@
|
|
1
|
+
import {MDCSelect} from '@material/select';
|
2
|
+
import {VBaseComponent} from './base-component';
|
3
|
+
import {eventHandlerMixin} from './mixins/event-handler';
|
4
|
+
|
5
|
+
export function initSelects() {
|
6
|
+
console.log('\tSelects');
|
7
|
+
var components = document.querySelectorAll('.mdc-select');
|
8
|
+
for (var i = 0; i < components.length; i++) {
|
9
|
+
var component = components[i];
|
10
|
+
if (!component.vComponent) {
|
11
|
+
let vSelect = new VSelect(component, MDCSelect.attachTo(component));
|
12
|
+
component.vComponent = vSelect;
|
13
|
+
var selectInput = component.querySelector('select');
|
14
|
+
selectInput.vComponent = vSelect;
|
15
|
+
}
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
|
20
|
+
export class VSelect extends eventHandlerMixin(VBaseComponent) {
|
21
|
+
constructor(element, mdcComponent) {
|
22
|
+
super(element);
|
23
|
+
this.select = element.querySelector('select');
|
24
|
+
this.mdcComponent = mdcComponent;
|
25
|
+
}
|
26
|
+
|
27
|
+
prepareSubmit(form, params) {
|
28
|
+
// On actual post/submit the form is passed and we are not expected to return our value
|
29
|
+
if (!form) {
|
30
|
+
params.push([this.select.name, this.select.value]);
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
validate() {
|
35
|
+
return true;
|
36
|
+
}
|
37
|
+
|
38
|
+
name(){
|
39
|
+
|
40
|
+
}
|
41
|
+
|
42
|
+
value(){
|
43
|
+
|
44
|
+
}
|
45
|
+
}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
import {MDCSnackbar, MDCSnackbarFoundation} from '@material/snackbar';
|
2
|
+
|
3
|
+
// This class displays a page level message
|
4
|
+
export class VSnackbar {
|
5
|
+
constructor(element, snackbar) {
|
6
|
+
this.element = element;
|
7
|
+
this.snackbar = snackbar;
|
8
|
+
}
|
9
|
+
|
10
|
+
display(message) {
|
11
|
+
const dataObj = {
|
12
|
+
message: message,
|
13
|
+
// actionText: 'Undo',
|
14
|
+
// actionHandler: function () {
|
15
|
+
// console.log('my undo function');
|
16
|
+
// }
|
17
|
+
};
|
18
|
+
this.snackbar.show(dataObj);
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
export function initSnackbar() {
|
23
|
+
console.log('\tSnackbar');
|
24
|
+
var components = document.querySelectorAll('.mdc-snackbar');
|
25
|
+
for (var i = 0; i < components.length; i++) {
|
26
|
+
var component = components[i];
|
27
|
+
if (!component.vComponent) {
|
28
|
+
let vSnackbar= new VSnackbar(component, MDCSnackbar.attachTo(component));
|
29
|
+
component.vComponent = vSnackbar;
|
30
|
+
}
|
31
|
+
}
|
32
|
+
}
|
@@ -0,0 +1,77 @@
|
|
1
|
+
import {MDCTextField} from '@material/textfield';
|
2
|
+
import {VBaseComponent} from './base-component';
|
3
|
+
import {eventHandlerMixin} from './mixins/event-handler';
|
4
|
+
|
5
|
+
export function initTextFields() {
|
6
|
+
console.log('\tTextFields');
|
7
|
+
|
8
|
+
var textFields = document.querySelectorAll('.mdc-text-field');
|
9
|
+
for (var i = 0; i < textFields.length; i++) {
|
10
|
+
var textField = textFields[i];
|
11
|
+
if (!textField.vComponent) {
|
12
|
+
var vTextField = new VTextField(textField, new MDCTextField(textField));
|
13
|
+
var input = textField.querySelector('input');
|
14
|
+
input.vComponent = vTextField;
|
15
|
+
textField.vComponent = vTextField;
|
16
|
+
}
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
export class VTextField extends eventHandlerMixin(VBaseComponent) {
|
21
|
+
constructor(element, mdcComponent) {
|
22
|
+
super(element);
|
23
|
+
this.input = element.querySelector('input');
|
24
|
+
this.mdcComponent = mdcComponent;
|
25
|
+
}
|
26
|
+
|
27
|
+
// Called whenever a form is about to be submitted.
|
28
|
+
// returns true on success
|
29
|
+
// returns on failure return an error object that can be processed by VErrors:
|
30
|
+
// { email: ["email must be filled", "email must be from your domain"] }
|
31
|
+
// { :page: ["must be filled"] }
|
32
|
+
validate(formData) {
|
33
|
+
console.log("TextField validate", formData);
|
34
|
+
let isValid = this.input.checkValidity();
|
35
|
+
if(isValid) {
|
36
|
+
return true;
|
37
|
+
}
|
38
|
+
let errorMessage = {};
|
39
|
+
errorMessage[this.input.id] = [this.input.validationMessage];
|
40
|
+
return errorMessage;
|
41
|
+
}
|
42
|
+
|
43
|
+
value(){
|
44
|
+
return this.input.value;
|
45
|
+
}
|
46
|
+
|
47
|
+
// Called to collect data for submission
|
48
|
+
prepareSubmit(form, params) {
|
49
|
+
var optionSelected = this.optionSelected();
|
50
|
+
if (optionSelected) {
|
51
|
+
var key = optionSelected.dataset.key;
|
52
|
+
if (key) {
|
53
|
+
var name = this.input.name;
|
54
|
+
var id = name + '_id';
|
55
|
+
params.push([id, key]);
|
56
|
+
console.log("TextField prepareSubmit added:" + id + '=' + key);
|
57
|
+
}
|
58
|
+
}
|
59
|
+
// On actual post/submit the form is passed and we are not expected to return our value
|
60
|
+
if (!form) {
|
61
|
+
params.push([this.input.name, this.input.value]);
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
optionSelected() {
|
66
|
+
var dataList = this.element.querySelector('datalist');
|
67
|
+
var parentElement = this.input;
|
68
|
+
|
69
|
+
// If we find the input inside our list, we submit the form
|
70
|
+
for (var element of dataList.children) {
|
71
|
+
if (element.value === parentElement.value) {
|
72
|
+
return element;
|
73
|
+
}
|
74
|
+
}
|
75
|
+
return null;
|
76
|
+
}
|
77
|
+
}
|
@@ -0,0 +1,738 @@
|
|
1
|
+
(function() {
|
2
|
+
|
3
|
+
// nb. This is for IE10 and lower _only_.
|
4
|
+
var supportCustomEvent = window.CustomEvent;
|
5
|
+
if (!supportCustomEvent || typeof supportCustomEvent === 'object') {
|
6
|
+
supportCustomEvent = function CustomEvent(event, x) {
|
7
|
+
x = x || {};
|
8
|
+
var ev = document.createEvent('CustomEvent');
|
9
|
+
ev.initCustomEvent(event, !!x.bubbles, !!x.cancelable, x.detail || null);
|
10
|
+
return ev;
|
11
|
+
};
|
12
|
+
supportCustomEvent.prototype = window.Event.prototype;
|
13
|
+
}
|
14
|
+
|
15
|
+
/**
|
16
|
+
* @param {Element} el to check for stacking context
|
17
|
+
* @return {boolean} whether this el or its parents creates a stacking context
|
18
|
+
*/
|
19
|
+
function createsStackingContext(el) {
|
20
|
+
while (el && el !== document.body) {
|
21
|
+
var s = window.getComputedStyle(el);
|
22
|
+
var invalid = function(k, ok) {
|
23
|
+
return !(s[k] === undefined || s[k] === ok);
|
24
|
+
}
|
25
|
+
if (s.opacity < 1 ||
|
26
|
+
invalid('zIndex', 'auto') ||
|
27
|
+
invalid('transform', 'none') ||
|
28
|
+
invalid('mixBlendMode', 'normal') ||
|
29
|
+
invalid('filter', 'none') ||
|
30
|
+
invalid('perspective', 'none') ||
|
31
|
+
s['isolation'] === 'isolate' ||
|
32
|
+
s.position === 'fixed' ||
|
33
|
+
s.webkitOverflowScrolling === 'touch') {
|
34
|
+
return true;
|
35
|
+
}
|
36
|
+
el = el.parentElement;
|
37
|
+
}
|
38
|
+
return false;
|
39
|
+
}
|
40
|
+
|
41
|
+
/**
|
42
|
+
* Finds the nearest <dialog> from the passed element.
|
43
|
+
*
|
44
|
+
* @param {Element} el to search from
|
45
|
+
* @return {HTMLDialogElement} dialog found
|
46
|
+
*/
|
47
|
+
function findNearestDialog(el) {
|
48
|
+
while (el) {
|
49
|
+
if (el.localName === 'dialog') {
|
50
|
+
return /** @type {HTMLDialogElement} */ (el);
|
51
|
+
}
|
52
|
+
el = el.parentElement;
|
53
|
+
}
|
54
|
+
return null;
|
55
|
+
}
|
56
|
+
|
57
|
+
/**
|
58
|
+
* Blur the specified element, as long as it's not the HTML body element.
|
59
|
+
* This works around an IE9/10 bug - blurring the body causes Windows to
|
60
|
+
* blur the whole application.
|
61
|
+
*
|
62
|
+
* @param {Element} el to blur
|
63
|
+
*/
|
64
|
+
function safeBlur(el) {
|
65
|
+
if (el && el.blur && el !== document.body) {
|
66
|
+
el.blur();
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
/**
|
71
|
+
* @param {!NodeList} nodeList to search
|
72
|
+
* @param {Node} node to find
|
73
|
+
* @return {boolean} whether node is inside nodeList
|
74
|
+
*/
|
75
|
+
function inNodeList(nodeList, node) {
|
76
|
+
for (var i = 0; i < nodeList.length; ++i) {
|
77
|
+
if (nodeList[i] === node) {
|
78
|
+
return true;
|
79
|
+
}
|
80
|
+
}
|
81
|
+
return false;
|
82
|
+
}
|
83
|
+
|
84
|
+
/**
|
85
|
+
* @param {HTMLFormElement} el to check
|
86
|
+
* @return {boolean} whether this form has method="dialog"
|
87
|
+
*/
|
88
|
+
function isFormMethodDialog(el) {
|
89
|
+
if (!el || !el.hasAttribute('method')) {
|
90
|
+
return false;
|
91
|
+
}
|
92
|
+
return el.getAttribute('method').toLowerCase() === 'dialog';
|
93
|
+
}
|
94
|
+
|
95
|
+
/**
|
96
|
+
* @param {!HTMLDialogElement} dialog to upgrade
|
97
|
+
* @constructor
|
98
|
+
*/
|
99
|
+
function dialogPolyfillInfo(dialog) {
|
100
|
+
this.dialog_ = dialog;
|
101
|
+
this.replacedStyleTop_ = false;
|
102
|
+
this.openAsModal_ = false;
|
103
|
+
|
104
|
+
// Set a11y role. Browsers that support dialog implicitly know this already.
|
105
|
+
if (!dialog.hasAttribute('role')) {
|
106
|
+
dialog.setAttribute('role', 'dialog');
|
107
|
+
}
|
108
|
+
|
109
|
+
dialog.show = this.show.bind(this);
|
110
|
+
dialog.showModal = this.showModal.bind(this);
|
111
|
+
dialog.close = this.close.bind(this);
|
112
|
+
|
113
|
+
if (!('returnValue' in dialog)) {
|
114
|
+
dialog.returnValue = '';
|
115
|
+
}
|
116
|
+
|
117
|
+
if ('MutationObserver' in window) {
|
118
|
+
var mo = new MutationObserver(this.maybeHideModal.bind(this));
|
119
|
+
mo.observe(dialog, {attributes: true, attributeFilter: ['open']});
|
120
|
+
} else {
|
121
|
+
// IE10 and below support. Note that DOMNodeRemoved etc fire _before_ removal. They also
|
122
|
+
// seem to fire even if the element was removed as part of a parent removal. Use the removed
|
123
|
+
// events to force downgrade (useful if removed/immediately added).
|
124
|
+
var removed = false;
|
125
|
+
var cb = function() {
|
126
|
+
removed ? this.downgradeModal() : this.maybeHideModal();
|
127
|
+
removed = false;
|
128
|
+
}.bind(this);
|
129
|
+
var timeout;
|
130
|
+
var delayModel = function(ev) {
|
131
|
+
if (ev.target !== dialog) { return; } // not for a child element
|
132
|
+
var cand = 'DOMNodeRemoved';
|
133
|
+
removed |= (ev.type.substr(0, cand.length) === cand);
|
134
|
+
window.clearTimeout(timeout);
|
135
|
+
timeout = window.setTimeout(cb, 0);
|
136
|
+
};
|
137
|
+
['DOMAttrModified', 'DOMNodeRemoved', 'DOMNodeRemovedFromDocument'].forEach(function(name) {
|
138
|
+
dialog.addEventListener(name, delayModel);
|
139
|
+
});
|
140
|
+
}
|
141
|
+
// Note that the DOM is observed inside DialogManager while any dialog
|
142
|
+
// is being displayed as a modal, to catch modal removal from the DOM.
|
143
|
+
|
144
|
+
Object.defineProperty(dialog, 'open', {
|
145
|
+
set: this.setOpen.bind(this),
|
146
|
+
get: dialog.hasAttribute.bind(dialog, 'open')
|
147
|
+
});
|
148
|
+
|
149
|
+
this.backdrop_ = document.createElement('div');
|
150
|
+
this.backdrop_.className = 'backdrop';
|
151
|
+
this.backdrop_.addEventListener('click', this.backdropClick_.bind(this));
|
152
|
+
}
|
153
|
+
|
154
|
+
dialogPolyfillInfo.prototype = {
|
155
|
+
|
156
|
+
get dialog() {
|
157
|
+
return this.dialog_;
|
158
|
+
},
|
159
|
+
|
160
|
+
/**
|
161
|
+
* Maybe remove this dialog from the modal top layer. This is called when
|
162
|
+
* a modal dialog may no longer be tenable, e.g., when the dialog is no
|
163
|
+
* longer open or is no longer part of the DOM.
|
164
|
+
*/
|
165
|
+
maybeHideModal: function() {
|
166
|
+
if (this.dialog_.hasAttribute('open') && document.body.contains(this.dialog_)) { return; }
|
167
|
+
this.downgradeModal();
|
168
|
+
},
|
169
|
+
|
170
|
+
/**
|
171
|
+
* Remove this dialog from the modal top layer, leaving it as a non-modal.
|
172
|
+
*/
|
173
|
+
downgradeModal: function() {
|
174
|
+
if (!this.openAsModal_) { return; }
|
175
|
+
this.openAsModal_ = false;
|
176
|
+
this.dialog_.style.zIndex = '';
|
177
|
+
|
178
|
+
// This won't match the native <dialog> exactly because if the user set top on a centered
|
179
|
+
// polyfill dialog, that top gets thrown away when the dialog is closed. Not sure it's
|
180
|
+
// possible to polyfill this perfectly.
|
181
|
+
if (this.replacedStyleTop_) {
|
182
|
+
this.dialog_.style.top = '';
|
183
|
+
this.replacedStyleTop_ = false;
|
184
|
+
}
|
185
|
+
|
186
|
+
// Clear the backdrop and remove from the manager.
|
187
|
+
this.backdrop_.parentNode && this.backdrop_.parentNode.removeChild(this.backdrop_);
|
188
|
+
dialogPolyfill.dm.removeDialog(this);
|
189
|
+
},
|
190
|
+
|
191
|
+
/**
|
192
|
+
* @param {boolean} value whether to open or close this dialog
|
193
|
+
*/
|
194
|
+
setOpen: function(value) {
|
195
|
+
if (value) {
|
196
|
+
this.dialog_.hasAttribute('open') || this.dialog_.setAttribute('open', '');
|
197
|
+
} else {
|
198
|
+
this.dialog_.removeAttribute('open');
|
199
|
+
this.maybeHideModal(); // nb. redundant with MutationObserver
|
200
|
+
}
|
201
|
+
},
|
202
|
+
|
203
|
+
/**
|
204
|
+
* Handles clicks on the fake .backdrop element, redirecting them as if
|
205
|
+
* they were on the dialog itself.
|
206
|
+
*
|
207
|
+
* @param {!Event} e to redirect
|
208
|
+
*/
|
209
|
+
backdropClick_: function(e) {
|
210
|
+
if (!this.dialog_.hasAttribute('tabindex')) {
|
211
|
+
// Clicking on the backdrop should move the implicit cursor, even if dialog cannot be
|
212
|
+
// focused. Create a fake thing to focus on. If the backdrop was _before_ the dialog, this
|
213
|
+
// would not be needed - clicks would move the implicit cursor there.
|
214
|
+
var fake = document.createElement('div');
|
215
|
+
this.dialog_.insertBefore(fake, this.dialog_.firstChild);
|
216
|
+
fake.tabIndex = -1;
|
217
|
+
fake.focus();
|
218
|
+
this.dialog_.removeChild(fake);
|
219
|
+
} else {
|
220
|
+
this.dialog_.focus();
|
221
|
+
}
|
222
|
+
|
223
|
+
var redirectedEvent = document.createEvent('MouseEvents');
|
224
|
+
redirectedEvent.initMouseEvent(e.type, e.bubbles, e.cancelable, window,
|
225
|
+
e.detail, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey,
|
226
|
+
e.altKey, e.shiftKey, e.metaKey, e.button, e.relatedTarget);
|
227
|
+
this.dialog_.dispatchEvent(redirectedEvent);
|
228
|
+
e.stopPropagation();
|
229
|
+
},
|
230
|
+
|
231
|
+
/**
|
232
|
+
* Focuses on the first focusable element within the dialog. This will always blur the current
|
233
|
+
* focus, even if nothing within the dialog is found.
|
234
|
+
*/
|
235
|
+
focus_: function() {
|
236
|
+
// Find element with `autofocus` attribute, or fall back to the first form/tabindex control.
|
237
|
+
var target = this.dialog_.querySelector('[autofocus]:not([disabled])');
|
238
|
+
if (!target && this.dialog_.tabIndex >= 0) {
|
239
|
+
target = this.dialog_;
|
240
|
+
}
|
241
|
+
if (!target) {
|
242
|
+
// Note that this is 'any focusable area'. This list is probably not exhaustive, but the
|
243
|
+
// alternative involves stepping through and trying to focus everything.
|
244
|
+
var opts = ['button', 'input', 'keygen', 'select', 'textarea'];
|
245
|
+
var query = opts.map(function(el) {
|
246
|
+
return el + ':not([disabled])';
|
247
|
+
});
|
248
|
+
// TODO(samthor): tabindex values that are not numeric are not focusable.
|
249
|
+
query.push('[tabindex]:not([disabled]):not([tabindex=""])'); // tabindex != "", not disabled
|
250
|
+
target = this.dialog_.querySelector(query.join(', '));
|
251
|
+
}
|
252
|
+
safeBlur(document.activeElement);
|
253
|
+
target && target.focus();
|
254
|
+
},
|
255
|
+
|
256
|
+
/**
|
257
|
+
* Sets the zIndex for the backdrop and dialog.
|
258
|
+
*
|
259
|
+
* @param {number} dialogZ
|
260
|
+
* @param {number} backdropZ
|
261
|
+
*/
|
262
|
+
updateZIndex: function(dialogZ, backdropZ) {
|
263
|
+
if (dialogZ < backdropZ) {
|
264
|
+
throw new Error('dialogZ should never be < backdropZ');
|
265
|
+
}
|
266
|
+
this.dialog_.style.zIndex = dialogZ;
|
267
|
+
this.backdrop_.style.zIndex = backdropZ;
|
268
|
+
},
|
269
|
+
|
270
|
+
/**
|
271
|
+
* Shows the dialog. If the dialog is already open, this does nothing.
|
272
|
+
*/
|
273
|
+
show: function() {
|
274
|
+
if (!this.dialog_.open) {
|
275
|
+
this.setOpen(true);
|
276
|
+
this.focus_();
|
277
|
+
}
|
278
|
+
},
|
279
|
+
|
280
|
+
/**
|
281
|
+
* Show this dialog modally.
|
282
|
+
*/
|
283
|
+
showModal: function() {
|
284
|
+
if (this.dialog_.hasAttribute('open')) {
|
285
|
+
throw new Error('Failed to execute \'showModal\' on dialog: The element is already open, and therefore cannot be opened modally.');
|
286
|
+
}
|
287
|
+
if (!document.body.contains(this.dialog_)) {
|
288
|
+
throw new Error('Failed to execute \'showModal\' on dialog: The element is not in a Document.');
|
289
|
+
}
|
290
|
+
if (!dialogPolyfill.dm.pushDialog(this)) {
|
291
|
+
throw new Error('Failed to execute \'showModal\' on dialog: There are too many open modal dialogs.');
|
292
|
+
}
|
293
|
+
|
294
|
+
if (createsStackingContext(this.dialog_.parentElement)) {
|
295
|
+
console.warn('A dialog is being shown inside a stacking context. ' +
|
296
|
+
'This may cause it to be unusable. For more information, see this link: ' +
|
297
|
+
'https://github.com/GoogleChrome/dialog-polyfill/#stacking-context');
|
298
|
+
}
|
299
|
+
|
300
|
+
this.setOpen(true);
|
301
|
+
this.openAsModal_ = true;
|
302
|
+
|
303
|
+
// Optionally center vertically, relative to the current viewport.
|
304
|
+
if (dialogPolyfill.needsCentering(this.dialog_)) {
|
305
|
+
dialogPolyfill.reposition(this.dialog_);
|
306
|
+
this.replacedStyleTop_ = true;
|
307
|
+
} else {
|
308
|
+
this.replacedStyleTop_ = false;
|
309
|
+
}
|
310
|
+
|
311
|
+
// Insert backdrop.
|
312
|
+
this.dialog_.parentNode.insertBefore(this.backdrop_, this.dialog_.nextSibling);
|
313
|
+
|
314
|
+
// Focus on whatever inside the dialog.
|
315
|
+
this.focus_();
|
316
|
+
},
|
317
|
+
|
318
|
+
/**
|
319
|
+
* Closes this HTMLDialogElement. This is optional vs clearing the open
|
320
|
+
* attribute, however this fires a 'close' event.
|
321
|
+
*
|
322
|
+
* @param {string=} opt_returnValue to use as the returnValue
|
323
|
+
*/
|
324
|
+
close: function(opt_returnValue) {
|
325
|
+
if (!this.dialog_.hasAttribute('open')) {
|
326
|
+
throw new Error('Failed to execute \'close\' on dialog: The element does not have an \'open\' attribute, and therefore cannot be closed.');
|
327
|
+
}
|
328
|
+
this.setOpen(false);
|
329
|
+
|
330
|
+
// Leave returnValue untouched in case it was set directly on the element
|
331
|
+
if (opt_returnValue !== undefined) {
|
332
|
+
this.dialog_.returnValue = opt_returnValue;
|
333
|
+
}
|
334
|
+
|
335
|
+
// Triggering "close" event for any attached listeners on the <dialog>.
|
336
|
+
var closeEvent = new supportCustomEvent('close', {
|
337
|
+
bubbles: false,
|
338
|
+
cancelable: false
|
339
|
+
});
|
340
|
+
this.dialog_.dispatchEvent(closeEvent);
|
341
|
+
}
|
342
|
+
|
343
|
+
};
|
344
|
+
|
345
|
+
var dialogPolyfill = {};
|
346
|
+
|
347
|
+
dialogPolyfill.reposition = function(element) {
|
348
|
+
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
|
349
|
+
var topValue = scrollTop + (window.innerHeight - element.offsetHeight) / 2;
|
350
|
+
element.style.top = Math.max(scrollTop, topValue) + 'px';
|
351
|
+
};
|
352
|
+
|
353
|
+
dialogPolyfill.isInlinePositionSetByStylesheet = function(element) {
|
354
|
+
for (var i = 0; i < document.styleSheets.length; ++i) {
|
355
|
+
var styleSheet = document.styleSheets[i];
|
356
|
+
var cssRules = null;
|
357
|
+
// Some browsers throw on cssRules.
|
358
|
+
try {
|
359
|
+
cssRules = styleSheet.cssRules;
|
360
|
+
} catch (e) {}
|
361
|
+
if (!cssRules) { continue; }
|
362
|
+
for (var j = 0; j < cssRules.length; ++j) {
|
363
|
+
var rule = cssRules[j];
|
364
|
+
var selectedNodes = null;
|
365
|
+
// Ignore errors on invalid selector texts.
|
366
|
+
try {
|
367
|
+
selectedNodes = document.querySelectorAll(rule.selectorText);
|
368
|
+
} catch(e) {}
|
369
|
+
if (!selectedNodes || !inNodeList(selectedNodes, element)) {
|
370
|
+
continue;
|
371
|
+
}
|
372
|
+
var cssTop = rule.style.getPropertyValue('top');
|
373
|
+
var cssBottom = rule.style.getPropertyValue('bottom');
|
374
|
+
if ((cssTop && cssTop !== 'auto') || (cssBottom && cssBottom !== 'auto')) {
|
375
|
+
return true;
|
376
|
+
}
|
377
|
+
}
|
378
|
+
}
|
379
|
+
return false;
|
380
|
+
};
|
381
|
+
|
382
|
+
dialogPolyfill.needsCentering = function(dialog) {
|
383
|
+
var computedStyle = window.getComputedStyle(dialog);
|
384
|
+
if (computedStyle.position !== 'absolute') {
|
385
|
+
return false;
|
386
|
+
}
|
387
|
+
|
388
|
+
// We must determine whether the top/bottom specified value is non-auto. In
|
389
|
+
// WebKit/Blink, checking computedStyle.top == 'auto' is sufficient, but
|
390
|
+
// Firefox returns the used value. So we do this crazy thing instead: check
|
391
|
+
// the inline style and then go through CSS rules.
|
392
|
+
if ((dialog.style.top !== 'auto' && dialog.style.top !== '') ||
|
393
|
+
(dialog.style.bottom !== 'auto' && dialog.style.bottom !== '')) {
|
394
|
+
return false;
|
395
|
+
}
|
396
|
+
return !dialogPolyfill.isInlinePositionSetByStylesheet(dialog);
|
397
|
+
};
|
398
|
+
|
399
|
+
/**
|
400
|
+
* @param {!Element} element to force upgrade
|
401
|
+
*/
|
402
|
+
dialogPolyfill.forceRegisterDialog = function(element) {
|
403
|
+
if (window.HTMLDialogElement || element.showModal) {
|
404
|
+
console.warn('This browser already supports <dialog>, the polyfill ' +
|
405
|
+
'may not work correctly', element);
|
406
|
+
}
|
407
|
+
if (element.localName !== 'dialog') {
|
408
|
+
throw new Error('Failed to register dialog: The element is not a dialog.');
|
409
|
+
}
|
410
|
+
new dialogPolyfillInfo(/** @type {!HTMLDialogElement} */ (element));
|
411
|
+
};
|
412
|
+
|
413
|
+
/**
|
414
|
+
* @param {!Element} element to upgrade, if necessary
|
415
|
+
*/
|
416
|
+
dialogPolyfill.registerDialog = function(element) {
|
417
|
+
if (!element.showModal) {
|
418
|
+
dialogPolyfill.forceRegisterDialog(element);
|
419
|
+
}
|
420
|
+
};
|
421
|
+
|
422
|
+
/**
|
423
|
+
* @constructor
|
424
|
+
*/
|
425
|
+
dialogPolyfill.DialogManager = function() {
|
426
|
+
/** @type {!Array<!dialogPolyfillInfo>} */
|
427
|
+
this.pendingDialogStack = [];
|
428
|
+
|
429
|
+
var checkDOM = this.checkDOM_.bind(this);
|
430
|
+
|
431
|
+
// The overlay is used to simulate how a modal dialog blocks the document.
|
432
|
+
// The blocking dialog is positioned on top of the overlay, and the rest of
|
433
|
+
// the dialogs on the pending dialog stack are positioned below it. In the
|
434
|
+
// actual implementation, the modal dialog stacking is controlled by the
|
435
|
+
// top layer, where z-index has no effect.
|
436
|
+
this.overlay = document.createElement('div');
|
437
|
+
this.overlay.className = '_dialog_overlay';
|
438
|
+
this.overlay.addEventListener('click', function(e) {
|
439
|
+
this.forwardTab_ = undefined;
|
440
|
+
e.stopPropagation();
|
441
|
+
checkDOM([]); // sanity-check DOM
|
442
|
+
}.bind(this));
|
443
|
+
|
444
|
+
this.handleKey_ = this.handleKey_.bind(this);
|
445
|
+
this.handleFocus_ = this.handleFocus_.bind(this);
|
446
|
+
|
447
|
+
this.zIndexLow_ = 100000;
|
448
|
+
this.zIndexHigh_ = 100000 + 150;
|
449
|
+
|
450
|
+
this.forwardTab_ = undefined;
|
451
|
+
|
452
|
+
if ('MutationObserver' in window) {
|
453
|
+
this.mo_ = new MutationObserver(function(records) {
|
454
|
+
var removed = [];
|
455
|
+
records.forEach(function(rec) {
|
456
|
+
for (var i = 0, c; c = rec.removedNodes[i]; ++i) {
|
457
|
+
if (!(c instanceof Element)) {
|
458
|
+
continue;
|
459
|
+
} else if (c.localName === 'dialog') {
|
460
|
+
removed.push(c);
|
461
|
+
}
|
462
|
+
removed = removed.concat(c.querySelectorAll('dialog'));
|
463
|
+
}
|
464
|
+
});
|
465
|
+
removed.length && checkDOM(removed);
|
466
|
+
});
|
467
|
+
}
|
468
|
+
};
|
469
|
+
|
470
|
+
/**
|
471
|
+
* Called on the first modal dialog being shown. Adds the overlay and related
|
472
|
+
* handlers.
|
473
|
+
*/
|
474
|
+
dialogPolyfill.DialogManager.prototype.blockDocument = function() {
|
475
|
+
document.documentElement.addEventListener('focus', this.handleFocus_, true);
|
476
|
+
document.addEventListener('keydown', this.handleKey_);
|
477
|
+
this.mo_ && this.mo_.observe(document, {childList: true, subtree: true});
|
478
|
+
};
|
479
|
+
|
480
|
+
/**
|
481
|
+
* Called on the first modal dialog being removed, i.e., when no more modal
|
482
|
+
* dialogs are visible.
|
483
|
+
*/
|
484
|
+
dialogPolyfill.DialogManager.prototype.unblockDocument = function() {
|
485
|
+
document.documentElement.removeEventListener('focus', this.handleFocus_, true);
|
486
|
+
document.removeEventListener('keydown', this.handleKey_);
|
487
|
+
this.mo_ && this.mo_.disconnect();
|
488
|
+
};
|
489
|
+
|
490
|
+
/**
|
491
|
+
* Updates the stacking of all known dialogs.
|
492
|
+
*/
|
493
|
+
dialogPolyfill.DialogManager.prototype.updateStacking = function() {
|
494
|
+
var zIndex = this.zIndexHigh_;
|
495
|
+
|
496
|
+
for (var i = 0, dpi; dpi = this.pendingDialogStack[i]; ++i) {
|
497
|
+
dpi.updateZIndex(--zIndex, --zIndex);
|
498
|
+
if (i === 0) {
|
499
|
+
this.overlay.style.zIndex = --zIndex;
|
500
|
+
}
|
501
|
+
}
|
502
|
+
|
503
|
+
// Make the overlay a sibling of the dialog itself.
|
504
|
+
var last = this.pendingDialogStack[0];
|
505
|
+
if (last) {
|
506
|
+
var p = last.dialog.parentNode || document.body;
|
507
|
+
p.appendChild(this.overlay);
|
508
|
+
} else if (this.overlay.parentNode) {
|
509
|
+
this.overlay.parentNode.removeChild(this.overlay);
|
510
|
+
}
|
511
|
+
};
|
512
|
+
|
513
|
+
/**
|
514
|
+
* @param {Element} candidate to check if contained or is the top-most modal dialog
|
515
|
+
* @return {boolean} whether candidate is contained in top dialog
|
516
|
+
*/
|
517
|
+
dialogPolyfill.DialogManager.prototype.containedByTopDialog_ = function(candidate) {
|
518
|
+
while (candidate = findNearestDialog(candidate)) {
|
519
|
+
for (var i = 0, dpi; dpi = this.pendingDialogStack[i]; ++i) {
|
520
|
+
if (dpi.dialog === candidate) {
|
521
|
+
return i === 0; // only valid if top-most
|
522
|
+
}
|
523
|
+
}
|
524
|
+
candidate = candidate.parentElement;
|
525
|
+
}
|
526
|
+
return false;
|
527
|
+
};
|
528
|
+
|
529
|
+
dialogPolyfill.DialogManager.prototype.handleFocus_ = function(event) {
|
530
|
+
if (this.containedByTopDialog_(event.target)) { return; }
|
531
|
+
|
532
|
+
event.preventDefault();
|
533
|
+
event.stopPropagation();
|
534
|
+
safeBlur(/** @type {Element} */ (event.target));
|
535
|
+
|
536
|
+
if (this.forwardTab_ === undefined) { return; } // move focus only from a tab key
|
537
|
+
|
538
|
+
var dpi = this.pendingDialogStack[0];
|
539
|
+
var dialog = dpi.dialog;
|
540
|
+
var position = dialog.compareDocumentPosition(event.target);
|
541
|
+
if (position & Node.DOCUMENT_POSITION_PRECEDING) {
|
542
|
+
if (this.forwardTab_) { // forward
|
543
|
+
dpi.focus_();
|
544
|
+
} else { // backwards
|
545
|
+
document.documentElement.focus();
|
546
|
+
}
|
547
|
+
} else {
|
548
|
+
// TODO: Focus after the dialog, is ignored.
|
549
|
+
}
|
550
|
+
|
551
|
+
return false;
|
552
|
+
};
|
553
|
+
|
554
|
+
dialogPolyfill.DialogManager.prototype.handleKey_ = function(event) {
|
555
|
+
this.forwardTab_ = undefined;
|
556
|
+
if (event.keyCode === 27) {
|
557
|
+
event.preventDefault();
|
558
|
+
event.stopPropagation();
|
559
|
+
var cancelEvent = new supportCustomEvent('cancel', {
|
560
|
+
bubbles: false,
|
561
|
+
cancelable: true
|
562
|
+
});
|
563
|
+
var dpi = this.pendingDialogStack[0];
|
564
|
+
if (dpi && dpi.dialog.dispatchEvent(cancelEvent)) {
|
565
|
+
dpi.dialog.close();
|
566
|
+
}
|
567
|
+
} else if (event.keyCode === 9) {
|
568
|
+
this.forwardTab_ = !event.shiftKey;
|
569
|
+
}
|
570
|
+
};
|
571
|
+
|
572
|
+
/**
|
573
|
+
* Finds and downgrades any known modal dialogs that are no longer displayed. Dialogs that are
|
574
|
+
* removed and immediately readded don't stay modal, they become normal.
|
575
|
+
*
|
576
|
+
* @param {!Array<!HTMLDialogElement>} removed that have definitely been removed
|
577
|
+
*/
|
578
|
+
dialogPolyfill.DialogManager.prototype.checkDOM_ = function(removed) {
|
579
|
+
// This operates on a clone because it may cause it to change. Each change also calls
|
580
|
+
// updateStacking, which only actually needs to happen once. But who removes many modal dialogs
|
581
|
+
// at a time?!
|
582
|
+
var clone = this.pendingDialogStack.slice();
|
583
|
+
clone.forEach(function(dpi) {
|
584
|
+
if (removed.indexOf(dpi.dialog) !== -1) {
|
585
|
+
dpi.downgradeModal();
|
586
|
+
} else {
|
587
|
+
dpi.maybeHideModal();
|
588
|
+
}
|
589
|
+
});
|
590
|
+
};
|
591
|
+
|
592
|
+
/**
|
593
|
+
* @param {!dialogPolyfillInfo} dpi
|
594
|
+
* @return {boolean} whether the dialog was allowed
|
595
|
+
*/
|
596
|
+
dialogPolyfill.DialogManager.prototype.pushDialog = function(dpi) {
|
597
|
+
var allowed = (this.zIndexHigh_ - this.zIndexLow_) / 2 - 1;
|
598
|
+
if (this.pendingDialogStack.length >= allowed) {
|
599
|
+
return false;
|
600
|
+
}
|
601
|
+
if (this.pendingDialogStack.unshift(dpi) === 1) {
|
602
|
+
this.blockDocument();
|
603
|
+
}
|
604
|
+
this.updateStacking();
|
605
|
+
return true;
|
606
|
+
};
|
607
|
+
|
608
|
+
/**
|
609
|
+
* @param {!dialogPolyfillInfo} dpi
|
610
|
+
*/
|
611
|
+
dialogPolyfill.DialogManager.prototype.removeDialog = function(dpi) {
|
612
|
+
var index = this.pendingDialogStack.indexOf(dpi);
|
613
|
+
if (index === -1) { return; }
|
614
|
+
|
615
|
+
this.pendingDialogStack.splice(index, 1);
|
616
|
+
if (this.pendingDialogStack.length === 0) {
|
617
|
+
this.unblockDocument();
|
618
|
+
}
|
619
|
+
this.updateStacking();
|
620
|
+
};
|
621
|
+
|
622
|
+
dialogPolyfill.dm = new dialogPolyfill.DialogManager();
|
623
|
+
dialogPolyfill.formSubmitter = null;
|
624
|
+
dialogPolyfill.useValue = null;
|
625
|
+
|
626
|
+
/**
|
627
|
+
* Installs global handlers, such as click listers and native method overrides. These are needed
|
628
|
+
* even if a no dialog is registered, as they deal with <form method="dialog">.
|
629
|
+
*/
|
630
|
+
if (window.HTMLDialogElement === undefined) {
|
631
|
+
|
632
|
+
/**
|
633
|
+
* If HTMLFormElement translates method="DIALOG" into 'get', then replace the descriptor with
|
634
|
+
* one that returns the correct value.
|
635
|
+
*/
|
636
|
+
var testForm = document.createElement('form');
|
637
|
+
testForm.setAttribute('method', 'dialog');
|
638
|
+
if (testForm.method !== 'dialog') {
|
639
|
+
var methodDescriptor = Object.getOwnPropertyDescriptor(HTMLFormElement.prototype, 'method');
|
640
|
+
if (methodDescriptor) {
|
641
|
+
// nb. Some older iOS and older PhantomJS fail to return the descriptor. Don't do anything
|
642
|
+
// and don't bother to update the element.
|
643
|
+
var realGet = methodDescriptor.get;
|
644
|
+
methodDescriptor.get = function() {
|
645
|
+
if (isFormMethodDialog(this)) {
|
646
|
+
return 'dialog';
|
647
|
+
}
|
648
|
+
return realGet.call(this);
|
649
|
+
};
|
650
|
+
var realSet = methodDescriptor.set;
|
651
|
+
methodDescriptor.set = function(v) {
|
652
|
+
if (typeof v === 'string' && v.toLowerCase() === 'dialog') {
|
653
|
+
return this.setAttribute('method', v);
|
654
|
+
}
|
655
|
+
return realSet.call(this, v);
|
656
|
+
};
|
657
|
+
Object.defineProperty(HTMLFormElement.prototype, 'method', methodDescriptor);
|
658
|
+
}
|
659
|
+
}
|
660
|
+
|
661
|
+
/**
|
662
|
+
* Global 'click' handler, to capture the <input type="submit"> or <button> element which has
|
663
|
+
* submitted a <form method="dialog">. Needed as Safari and others don't report this inside
|
664
|
+
* document.activeElement.
|
665
|
+
*/
|
666
|
+
document.addEventListener('click', function(ev) {
|
667
|
+
dialogPolyfill.formSubmitter = null;
|
668
|
+
dialogPolyfill.useValue = null;
|
669
|
+
if (ev.defaultPrevented) { return; } // e.g. a submit which prevents default submission
|
670
|
+
|
671
|
+
var target = /** @type {Element} */ (ev.target);
|
672
|
+
if (!target || !isFormMethodDialog(target.form)) { return; }
|
673
|
+
|
674
|
+
var valid = (target.type === 'submit' && ['button', 'input'].indexOf(target.localName) > -1);
|
675
|
+
if (!valid) {
|
676
|
+
if (!(target.localName === 'input' && target.type === 'image')) { return; }
|
677
|
+
// this is a <input type="image">, which can submit forms
|
678
|
+
dialogPolyfill.useValue = ev.offsetX + ',' + ev.offsetY;
|
679
|
+
}
|
680
|
+
|
681
|
+
var dialog = findNearestDialog(target);
|
682
|
+
if (!dialog) { return; }
|
683
|
+
|
684
|
+
dialogPolyfill.formSubmitter = target;
|
685
|
+
}, false);
|
686
|
+
|
687
|
+
/**
|
688
|
+
* Replace the native HTMLFormElement.submit() method, as it won't fire the
|
689
|
+
* submit event and give us a chance to respond.
|
690
|
+
*/
|
691
|
+
var nativeFormSubmit = HTMLFormElement.prototype.submit;
|
692
|
+
var replacementFormSubmit = function () {
|
693
|
+
if (!isFormMethodDialog(this)) {
|
694
|
+
return nativeFormSubmit.call(this);
|
695
|
+
}
|
696
|
+
var dialog = findNearestDialog(this);
|
697
|
+
dialog && dialog.close();
|
698
|
+
};
|
699
|
+
HTMLFormElement.prototype.submit = replacementFormSubmit;
|
700
|
+
|
701
|
+
/**
|
702
|
+
* Global form 'dialog' method handler. Closes a dialog correctly on submit
|
703
|
+
* and possibly sets its return value.
|
704
|
+
*/
|
705
|
+
document.addEventListener('submit', function(ev) {
|
706
|
+
var form = /** @type {HTMLFormElement} */ (ev.target);
|
707
|
+
if (!isFormMethodDialog(form)) { return; }
|
708
|
+
ev.preventDefault();
|
709
|
+
|
710
|
+
var dialog = findNearestDialog(form);
|
711
|
+
if (!dialog) { return; }
|
712
|
+
|
713
|
+
// Forms can only be submitted via .submit() or a click (?), but anyway: sanity-check that
|
714
|
+
// the submitter is correct before using its value as .returnValue.
|
715
|
+
var s = dialogPolyfill.formSubmitter;
|
716
|
+
if (s && s.form === form) {
|
717
|
+
dialog.close(dialogPolyfill.useValue || s.value);
|
718
|
+
} else {
|
719
|
+
dialog.close();
|
720
|
+
}
|
721
|
+
dialogPolyfill.formSubmitter = null;
|
722
|
+
}, true);
|
723
|
+
}
|
724
|
+
|
725
|
+
dialogPolyfill['forceRegisterDialog'] = dialogPolyfill.forceRegisterDialog;
|
726
|
+
dialogPolyfill['registerDialog'] = dialogPolyfill.registerDialog;
|
727
|
+
|
728
|
+
if (typeof define === 'function' && 'amd' in define) {
|
729
|
+
// AMD support
|
730
|
+
define(function() { return dialogPolyfill; });
|
731
|
+
} else if (typeof module === 'object' && typeof module['exports'] === 'object') {
|
732
|
+
// CommonJS support
|
733
|
+
module['exports'] = dialogPolyfill;
|
734
|
+
} else {
|
735
|
+
// all others
|
736
|
+
window['dialogPolyfill'] = dialogPolyfill;
|
737
|
+
}
|
738
|
+
})();
|