mv com/google/domain/registry google/registry
This change renames directories in preparation for the great package rename. The repository is now in a broken state because the code itself hasn't been updated. However this should ensure that git correctly preserves history for each file.
159
java/google/registry/ui/BUILD
Normal file
|
@ -0,0 +1,159 @@
|
|||
package(default_visibility = ["//java/com/google/domain/registry:registry_project"])
|
||||
|
||||
load("//third_party/closure/compiler:closure_js_binary.bzl", "closure_js_binary")
|
||||
load("//third_party/closure/compiler:closure_js_deps.bzl", "closure_js_deps")
|
||||
|
||||
exports_files(["globals.txt"])
|
||||
|
||||
|
||||
filegroup(
|
||||
name = "assets_recursive",
|
||||
srcs = glob(["assets/**"]),
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "js_files_recursive",
|
||||
srcs = [
|
||||
"//java/com/google/domain/registry/ui/js:js_files",
|
||||
"//java/com/google/domain/registry/ui/js/registrar:js_files",
|
||||
"//java/com/google/domain/registry/ui/soy:js_files",
|
||||
"//java/com/google/domain/registry/ui/soy/api:js_files",
|
||||
"//java/com/google/domain/registry/ui/soy/registrar:js_files",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "runfiles",
|
||||
srcs = [
|
||||
"brain_bin.js",
|
||||
"brain_bin_map.js",
|
||||
"registrar_bin.js",
|
||||
"registrar_bin_map.js",
|
||||
":assets_recursive",
|
||||
"//java/com/google/domain/registry/ui:deps-runfiles.js",
|
||||
"//java/com/google/domain/registry/ui/css:registrar_bin.css",
|
||||
"//java/com/google/domain/registry/ui/html:html_files",
|
||||
"//javascript/closure:js_files_recursive",
|
||||
"//javascript/template/soy:soy_usegoog_js_files",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "runfiles_debug",
|
||||
srcs = [
|
||||
"deps-runfiles.js",
|
||||
":js_files_recursive",
|
||||
":runfiles",
|
||||
"//java/com/google/domain/registry/ui/css:css_files",
|
||||
"//javascript/closure:js_files_recursive",
|
||||
"//javascript/template/soy:soy_usegoog_js_files",
|
||||
"//third_party/javascript/closure:js_files_recursive",
|
||||
],
|
||||
)
|
||||
|
||||
java_library(
|
||||
name = "ui",
|
||||
srcs = glob(["*.java"]),
|
||||
deps = [
|
||||
"//java/com/google/common/base",
|
||||
"//java/com/google/domain/registry/config",
|
||||
"//third_party/java/appengine:appengine-api",
|
||||
"//third_party/java/dagger",
|
||||
"//third_party/java/jsr305_annotations",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
closure_js_deps(
|
||||
name = "deps",
|
||||
srcs = [
|
||||
"//java/com/google/domain/registry/ui/js",
|
||||
"//java/com/google/domain/registry/ui/js/registrar",
|
||||
],
|
||||
)
|
||||
|
||||
SOURCEMAP_SCRUB = (" -e 's@b....-out/[^/]*/bin/@@g'" +
|
||||
" -e 's@b....-out/[^/]*/genfiles/@@g'" +
|
||||
" -e 's@\"java/@\"/assets/sources/java/@g'" +
|
||||
" -e 's@\"javascript/@\"/assets/sources/javascript/@g'" +
|
||||
" -e 's@\"third_party/@\"/assets/sources/third_party/@g'" +
|
||||
" -e 's@\"external/@\"/assets/sources/external/@g'")
|
||||
|
||||
################################################################################
|
||||
## Registrar Console
|
||||
|
||||
# This is the full-blown compiled JavaScript source code for the registrar
|
||||
# console. Everything, including the soy templates, is compiled into a single
|
||||
# .js file. The only symbols that will be available are the ones you @export.
|
||||
# This will also replace calls to goog.getCssName() and {css ...} with their
|
||||
# non-union minified equivalents.
|
||||
closure_js_binary(
|
||||
name = "registrar_bin",
|
||||
externs_list = ["//java/com/google/domain/registry/ui/externs"],
|
||||
main = "registry.registrar.main",
|
||||
deps = [
|
||||
"//java/com/google/domain/registry/ui/css:registrar_bin",
|
||||
"//java/com/google/domain/registry/ui/js/registrar",
|
||||
],
|
||||
)
|
||||
|
||||
# Mangle all the paths in the generated sourcemap so they're absolute; assuming
|
||||
# the codebase is available under /assets/sources/.
|
||||
genrule(
|
||||
name = "registrar_bin_sourcemap",
|
||||
srcs = ["registrar_bin.sourcemap"],
|
||||
outs = ["registrar_bin.js.map"],
|
||||
cmd = "sed $(location registrar_bin.sourcemap) " + SOURCEMAP_SCRUB + " >$@",
|
||||
)
|
||||
|
||||
# The webserver should provide this to trusted admin users, rather than
|
||||
# registrar_bin.js. This is what makes debugging in production possible.
|
||||
genrule(
|
||||
name = "registrar_mapped",
|
||||
srcs = ["registrar_bin.js"],
|
||||
outs = ["registrar_bin_map.js"],
|
||||
cmd = "cat $(location registrar_bin.js) >$@ && " +
|
||||
"echo '//# sourceMappingURL=registrar_bin.js.map' >>$@",
|
||||
)
|
||||
|
||||
# This target creates a compiled JavaScript file where symbols are renamed to
|
||||
# include dollar signs. This is useful for testing, because you can still read
|
||||
# the source code, but it'll fail if your code is incorrect with regard to
|
||||
# dotted and quoted properties. The same applies to CSS class names, which get
|
||||
# an extra underscore.
|
||||
closure_js_binary(
|
||||
name = "registrar_dbg",
|
||||
debug = 1,
|
||||
externs_list = ["//java/com/google/domain/registry/ui/externs"],
|
||||
formatting = "PRETTY_PRINT",
|
||||
main = "registry.registrar.main",
|
||||
deps = [
|
||||
"//java/com/google/domain/registry/ui/css:registrar_dbg",
|
||||
"//java/com/google/domain/registry/ui/js/registrar",
|
||||
],
|
||||
)
|
||||
|
||||
################################################################################
|
||||
## Braintree Payment Method Frame (Brainframe)
|
||||
|
||||
closure_js_binary(
|
||||
name = "brain_bin",
|
||||
externs_list = ["//java/com/google/domain/registry/ui/externs"],
|
||||
main = "registry.registrar.BrainFrame.main",
|
||||
deps = ["//java/com/google/domain/registry/ui/js/registrar"],
|
||||
)
|
||||
|
||||
genrule(
|
||||
name = "brain_bin_sourcemap",
|
||||
srcs = ["brain_bin.sourcemap"],
|
||||
outs = ["brain_bin.js.map"],
|
||||
cmd = "sed $(location brain_bin.sourcemap) " + SOURCEMAP_SCRUB + " >$@",
|
||||
)
|
||||
|
||||
genrule(
|
||||
name = "brain_mapped",
|
||||
srcs = ["brain_bin.js"],
|
||||
outs = ["brain_bin_map.js"],
|
||||
cmd = "cat $(location brain_bin.js) >$@ && " +
|
||||
"echo '//# sourceMappingURL=brain_bin.js.map' >>$@",
|
||||
)
|
54
java/google/registry/ui/ConsoleConfigModule.java
Normal file
|
@ -0,0 +1,54 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package com.google.domain.registry.ui;
|
||||
|
||||
import com.google.appengine.api.users.UserService;
|
||||
import com.google.domain.registry.config.ConfigModule.Config;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
/** Dagger module for UI configuration. */
|
||||
@Module
|
||||
public final class ConsoleConfigModule { // TODO(b/26829015): Move to config package.
|
||||
|
||||
@Provides
|
||||
static ConsoleDebug provideConsoleDebug() {
|
||||
return ConsoleDebug.get();
|
||||
}
|
||||
|
||||
/** URL of Braintree iframe sandbox iframe static HTML file. */
|
||||
@Provides
|
||||
@Config("brainframe")
|
||||
static String provideBrainframe(
|
||||
ConsoleDebug debug,
|
||||
UserService userService,
|
||||
@Config("projectId") String projectId) {
|
||||
switch (debug) {
|
||||
case PRODUCTION:
|
||||
return String.format("https://%s.storage.googleapis.com/%s",
|
||||
projectId,
|
||||
userService.isUserLoggedIn() && userService.isUserAdmin()
|
||||
? "brainframe-map.html"
|
||||
: "brainframe.html");
|
||||
case DEBUG:
|
||||
case RAW:
|
||||
case TEST:
|
||||
return "/assets/html/insecure-brainframe.html";
|
||||
default:
|
||||
throw new AssertionError(debug.toString());
|
||||
}
|
||||
}
|
||||
}
|
47
java/google/registry/ui/ConsoleDebug.java
Normal file
|
@ -0,0 +1,47 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package com.google.domain.registry.ui;
|
||||
|
||||
/** Enum defining which JS/CSS files get rendered in a soy templates. */
|
||||
public enum ConsoleDebug {
|
||||
|
||||
/** Use compiled CSS and JS. */
|
||||
PRODUCTION,
|
||||
|
||||
/** Use debug compiled CSS and JS, where symbols are only <i>slightly</i> mangled. */
|
||||
DEBUG,
|
||||
|
||||
/**
|
||||
* Use debug compiled CSS and raw JS from internal source code dependency-managed directory
|
||||
* structure.
|
||||
*/
|
||||
RAW,
|
||||
|
||||
/** Don't use any CSS or JS. This is used by JSTD unit tests. */
|
||||
TEST;
|
||||
|
||||
private static final String PROPERTY = "console.debug";
|
||||
private static final String DEFAULT = PRODUCTION.name();
|
||||
|
||||
/** Returns value configured by system property {@code #PROPERTY}. */
|
||||
public static ConsoleDebug get() {
|
||||
return valueOf(System.getProperty(PROPERTY, DEFAULT));
|
||||
}
|
||||
|
||||
/** Sets the global {@link ConsoleDebug} state. */
|
||||
public static void set(ConsoleDebug value) {
|
||||
System.setProperty(PROPERTY, value.toString());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<!doctype html>
|
||||
<!-- gsutil cp -a public-read -z html brainframe-alpha.html gs://domain-registry-alpha/brainframe.html -->
|
||||
<!-- curl https://domain-registry-alpha.storage.googleapis.com/brainframe.html -->
|
||||
<script src="https://js.braintreegateway.com/v2/braintree.js"></script>
|
||||
<script src="https://domain-registry-alpha.appspot.com/assets/js/brain_bin_map.js"></script>
|
||||
<body style="margin:0">
|
||||
<form><div id="brainframe"></div></form>
|
||||
<script>registry.registrar.BrainFrame.main('https://domain-registry-alpha.appspot.com', 'brainframe');</script>
|
13
java/google/registry/ui/assets/html/brainframe-map.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
<!doctype html>
|
||||
<!-- Source-mapped production iframe sandbox for Braintree iframe -->
|
||||
<!-- -->
|
||||
<!-- This is the same thing as brainframe.html except it allows admins of -->
|
||||
<!-- the App Engine application to see the original source code in Chrome's -->
|
||||
<!-- debugger if an exception is thrown by the minified JavaScript blob. -->
|
||||
<!-- -->
|
||||
<!-- gsutil cp -a public-read -z html brainframe-map.html gs://domain-registry/brainframe-map.html -->
|
||||
<script src="https://js.braintreegateway.com/v2/braintree.js"></script>
|
||||
<script src="https://domain-registry.appspot.com/assets/js/brain_bin_map.js"></script>
|
||||
<body style="margin:0">
|
||||
<form><div id="brainframe"></div></form>
|
||||
<script>registry.registrar.BrainFrame.main('https://domain-registry.appspot.com', 'brainframe');</script>
|
14
java/google/registry/ui/assets/html/brainframe.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
<!doctype html>
|
||||
<!-- Production iframe sandbox for Braintree iframe -->
|
||||
<!-- -->
|
||||
<!-- In order to securely embed Braintree's iframe, this iframe must be -->
|
||||
<!-- served from a separate origin. To do this, we manually deploy this -->
|
||||
<!-- as a static HTML file to a cloud storage bucket, as follows: -->
|
||||
<!-- -->
|
||||
<!-- gsutil cp -a public-read -z html brainframe.html gs://domain-registry/brainframe.html -->
|
||||
<!-- curl https://domain-registry.storage.googleapis.com/brainframe.html -->
|
||||
<script src="https://js.braintreegateway.com/v2/braintree.js"></script>
|
||||
<script src="https://domain-registry.appspot.com/assets/js/brain_bin.js"></script>
|
||||
<body style="margin:0">
|
||||
<form><div id="brainframe"></div></form>
|
||||
<script>registry.registrar.BrainFrame.main('https://domain-registry.appspot.com', 'brainframe');</script>
|
10
java/google/registry/ui/assets/html/insecure-brainframe.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
<!doctype html>
|
||||
<!-- This can only be used by admins in a testing environment. -->
|
||||
<!-- This iframe provides no isolation of Braintree from Console. -->
|
||||
<script src="https://js.braintreegateway.com/v2/braintree.js"></script>
|
||||
<script src="/assets/sources/javascript/closure/base.js"></script>
|
||||
<script src="/assets/sources/deps-runfiles.js"></script>
|
||||
<body style="margin:0">
|
||||
<form><div id="brainframe"></div></form>
|
||||
<script>goog.require('registry.registrar.BrainFrame.main');</script>
|
||||
<script>registry.registrar.BrainFrame.main('*', 'brainframe');</script>
|
|
@ -0,0 +1,4 @@
|
|||
<!doctype html>
|
||||
<!-- Brainframe for integration testing. -->
|
||||
<form><div id="brainframe"></div></form>
|
||||
<script>window.parent.postMessage('{"type": "payment_method", "method": {"type": "CreditCard", "nonce": "imanonce", "details": {"cardType": "Visa", "lastTwo": "00"}}}', '*');</script>
|
BIN
java/google/registry/ui/assets/images/ajax-loader.gif
Normal file
After Width: | Height: | Size: 673 B |
BIN
java/google/registry/ui/assets/images/android_sad.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 138 B |
After Width: | Height: | Size: 138 B |
After Width: | Height: | Size: 184 B |
BIN
java/google/registry/ui/assets/images/explore_24.png
Normal file
After Width: | Height: | Size: 1,016 B |
BIN
java/google/registry/ui/assets/images/folder.png
Normal file
After Width: | Height: | Size: 309 B |
BIN
java/google/registry/ui/assets/images/glogo_black.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
java/google/registry/ui/assets/images/google.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
java/google/registry/ui/assets/images/homeContact.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
java/google/registry/ui/assets/images/homeResources.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
java/google/registry/ui/assets/images/homeSettings.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
java/google/registry/ui/assets/images/ic_contacts_blue_12.png
Normal file
After Width: | Height: | Size: 535 B |
BIN
java/google/registry/ui/assets/images/ic_contacts_blue_8.png
Normal file
After Width: | Height: | Size: 327 B |
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
|
||||
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
|
||||
]>
|
||||
<svg version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
|
||||
x="0px" y="0px" width="21px" height="21px" viewBox="0 0 21 21" overflow="visible" enable-background="new 0 0 21 21"
|
||||
xml:space="preserve">
|
||||
<defs>
|
||||
</defs>
|
||||
<path fill="#FFFFFF" d="M17.646,16.501L14,12.855c0.566-0.811,0.903-1.792,0.903-2.855c0-2.762-2.238-5-5-5c-2.761,0-5,2.238-5,5
|
||||
s2.239,5,5,5c1.064,0,2.045-0.337,2.856-0.903l3.646,3.646c0.343,0.343,0.898,0.344,1.241,0.001
|
||||
C17.99,17.399,17.99,16.845,17.646,16.501z M9.903,13.2c-1.767,0-3.199-1.433-3.199-3.2s1.433-3.2,3.199-3.2
|
||||
c1.77,0,3.199,1.433,3.199,3.2S11.673,13.2,9.903,13.2z"/>
|
||||
<rect opacity="0" fill="#4387FD" width="21" height="21"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1 KiB |
17
java/google/registry/ui/assets/images/icons/svg/search.svg
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
|
||||
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
|
||||
]>
|
||||
<svg version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
|
||||
x="0px" y="0px" width="21px" height="21px" viewBox="0 0 21 21" overflow="visible" enable-background="new 0 0 21 21"
|
||||
xml:space="preserve">
|
||||
<defs>
|
||||
</defs>
|
||||
<path fill="#FFFFFF" d="M17.646,16.501L14,12.855c0.566-0.811,0.903-1.792,0.903-2.855c0-2.762-2.238-5-5-5c-2.761,0-5,2.238-5,5
|
||||
s2.239,5,5,5c1.064,0,2.045-0.337,2.856-0.903l3.646,3.646c0.343,0.343,0.898,0.344,1.241,0.001
|
||||
C17.99,17.399,17.99,16.845,17.646,16.501z M9.903,13.2c-1.767,0-3.199-1.433-3.199-3.2s1.433-3.2,3.199-3.2
|
||||
c1.77,0,3.199,1.433,3.199,3.2S11.673,13.2,9.903,13.2z"/>
|
||||
<rect opacity="0" fill="#4387FD" width="21" height="21"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1 KiB |
19
java/google/registry/ui/assets/images/icons/svg/settings.svg
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
|
||||
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
|
||||
]>
|
||||
<svg version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
|
||||
x="0px" y="0px" width="21px" height="21px" viewBox="0 0 21 21" overflow="visible" enable-background="new 0 0 21 21"
|
||||
xml:space="preserve">
|
||||
<defs>
|
||||
</defs>
|
||||
<path d="M16.895,11.64C16.96,11.269,17,10.89,17,10.5s-0.04-0.77-0.106-1.14l1.535-1.129l0.183-0.683l-1.5-2.598l-0.684-0.184
|
||||
l-1.738,0.762c-0.581-0.49-1.25-0.875-1.979-1.138L12.5,2.5L12,2H9L8.5,2.499L8.29,4.395C7.562,4.658,6.896,5.043,6.314,5.531
|
||||
L4.571,4.768L3.889,4.951l-1.5,2.598l0.183,0.684l1.535,1.129C4.04,9.731,4,10.111,4,10.5s0.04,0.769,0.106,1.139l-1.535,1.129
|
||||
l-0.183,0.684l1.5,2.598l0.682,0.184l1.744-0.764c0.58,0.488,1.248,0.873,1.975,1.137l0.21,1.896L9,19h3l0.5-0.5l0.21-1.892
|
||||
c0.729-0.263,1.398-0.648,1.979-1.138l1.738,0.762l0.684-0.184l1.499-2.598l-0.181-0.683L16.895,11.64z M14.15,10.5
|
||||
c0,2.016-1.635,3.65-3.65,3.65c-2.016,0-3.65-1.635-3.65-3.65s1.635-3.65,3.65-3.65C12.515,6.85,14.15,8.484,14.15,10.5z"/>
|
||||
<rect opacity="0" fill="#4387FD" width="21" height="21"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
BIN
java/google/registry/ui/assets/images/loader1x.gif
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
java/google/registry/ui/assets/images/loader4x.gif
Normal file
After Width: | Height: | Size: 79 KiB |
BIN
java/google/registry/ui/assets/images/logo_sm.gif
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
java/google/registry/ui/assets/images/offline_lightning.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
java/google/registry/ui/assets/images/ribbon_certified-128.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
java/google/registry/ui/assets/images/ribbon_certified-64.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
java/google/registry/ui/assets/images/visibleOff_16.png
Normal file
After Width: | Height: | Size: 556 B |
BIN
java/google/registry/ui/assets/images/visibleOn_16.png
Normal file
After Width: | Height: | Size: 480 B |
24
java/google/registry/ui/compile_test.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
/**
|
||||
* @fileoverview Test existing solely to run the :check BUILD rule.
|
||||
*/
|
||||
|
||||
goog.setTestOnly();
|
||||
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
|
||||
function testNothing() {}
|
8
java/google/registry/ui/conformance_proto.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
# -*-protobuf-*-
|
||||
|
||||
requirement: {
|
||||
type: BANNED_PROPERTY_WRITE
|
||||
error_message: 'Assignment to Element.prototype.innerHTML is not allowed. '
|
||||
'Use goog.dom.safe.setInnerHtml instead. '
|
||||
value: 'Element.prototype.innerHTML'
|
||||
}
|
50
java/google/registry/ui/css/BUILD
Normal file
|
@ -0,0 +1,50 @@
|
|||
package(default_visibility = ["//java/com/google/domain/registry:registry_project"])
|
||||
|
||||
load("//third_party/closure/stylesheets:closure_css_library.bzl", "closure_css_library")
|
||||
load("//third_party/closure/stylesheets:closure_css_binary.bzl", "closure_css_binary")
|
||||
|
||||
|
||||
filegroup(
|
||||
name = "css_files",
|
||||
srcs = glob(["*.css"]),
|
||||
)
|
||||
|
||||
closure_css_library(
|
||||
name = "kd_components_lib",
|
||||
srcs = ["kd_components.css"],
|
||||
)
|
||||
|
||||
|
||||
closure_css_library(
|
||||
name = "registry_lib",
|
||||
srcs = [
|
||||
"console.css",
|
||||
"forms.css",
|
||||
"registry.css",
|
||||
],
|
||||
deps = [":kd_components_lib"],
|
||||
)
|
||||
|
||||
closure_css_library(
|
||||
name = "registrar_lib",
|
||||
srcs = [
|
||||
"contact-settings.css",
|
||||
"contact-us.css",
|
||||
"dashboard.css",
|
||||
"epp.css",
|
||||
"resources.css",
|
||||
"security-settings.css",
|
||||
],
|
||||
deps = [":registry_lib"],
|
||||
)
|
||||
|
||||
closure_css_binary(
|
||||
name = "registrar_bin",
|
||||
deps = [":registrar_lib"],
|
||||
)
|
||||
|
||||
closure_css_binary(
|
||||
name = "registrar_dbg",
|
||||
debug = 1,
|
||||
deps = [":registrar_lib"],
|
||||
)
|
102
java/google/registry/ui/css/console.css
Normal file
|
@ -0,0 +1,102 @@
|
|||
.description {
|
||||
display: block;
|
||||
clear: both;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
line-height: 140%;
|
||||
}
|
||||
|
||||
.whoAreYou {
|
||||
width: 50%;
|
||||
margin: 5em auto;
|
||||
}
|
||||
|
||||
/* Console disabled page. */
|
||||
.whoAreYou-disabled {
|
||||
width: 50%;
|
||||
margin: 5em auto;
|
||||
}
|
||||
|
||||
.whoAreYou-disabled p img {
|
||||
display: block;
|
||||
margin: 3em auto;
|
||||
}
|
||||
|
||||
.whoAreYou-disabled h1 {
|
||||
border-top: solid 1px #ebebeb;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
/* XXX: Should be re-enabled when search works. */
|
||||
#kd-search {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* XXX: Should be generalized for use throughout console. */
|
||||
div.domain-registrar-contact div.tooltip {
|
||||
visibility: hidden;
|
||||
/* XXX: Should have auto-width. */
|
||||
width: 110px;
|
||||
left: -55px;
|
||||
height: 1em;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
color: white;
|
||||
background: #2d2d2d;
|
||||
padding: 0.5em;
|
||||
z-index: 2000;
|
||||
margin-top: -30px;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
div.domain-registrar-contact div.tooltip .pointer {
|
||||
outline: none;
|
||||
display: block;
|
||||
position: relative;
|
||||
bottom: -7px;
|
||||
left: 55px;
|
||||
margin: 0 0 0 -5px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
line-height: 0px;
|
||||
font-size: 0px;
|
||||
/* This sets the tooptip pointer color */
|
||||
border-bottom: transparent;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
border-top: 5px solid #2d2d2d;
|
||||
}
|
||||
|
||||
.reg-cryingAndroid {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.reg-bullets {
|
||||
padding-left: 1em;
|
||||
list-style: disc inside;
|
||||
}
|
||||
|
||||
.reg-payment p {
|
||||
max-width: 45em;
|
||||
}
|
||||
|
||||
/* Note: Empty definitions are for class name minimization. */
|
||||
|
||||
.reg-payment-form {}
|
||||
|
||||
.reg-payment-form-method {}
|
||||
|
||||
.reg-payment-form-method.kd-formerror {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.reg-payment-form-method-info {}
|
||||
|
||||
.reg-payment-form-submit {}
|
||||
|
||||
.reg-payment-form-loader {
|
||||
vertical-align: middle;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.reg-payment-again {}
|
58
java/google/registry/ui/css/contact-settings.css
Normal file
|
@ -0,0 +1,58 @@
|
|||
.domain-registrar-contacts {
|
||||
vertical-align: top;
|
||||
text-align: top;
|
||||
}
|
||||
|
||||
.domain-registrar-contact {
|
||||
display: table-cell;
|
||||
padding-right: 4em;
|
||||
padding-bottom: 2em;
|
||||
}
|
||||
|
||||
.domain-registrar-contact div {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
.domain-registrar-contact + br {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.domain-registrar-contact-name {
|
||||
font-weight: bold;
|
||||
display: inline;
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
.domain-registrar-contact-name i {
|
||||
float: right;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.domain-registrar-contact-name i.domain-registrar-contact-visible-in-whois {
|
||||
background: url('/assets/images/visibleOn_16.png') no-repeat right;
|
||||
}
|
||||
|
||||
/** Postal style for address. */
|
||||
td.setting-group-compact div.contact-address-city,
|
||||
td.setting-group-compact div.contact-address-state,
|
||||
td.setting-group-compact div.contact-address-zip,
|
||||
td.setting-group-compact div.contact-address-cc {
|
||||
width: initial;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/** Back to regular box flow for phone. */
|
||||
td.setting-group-compact input#phoneNumber {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vertical align shim for contact tds. Using border here instead of
|
||||
* padding since this is a table and padding is ignored.
|
||||
*
|
||||
* @see td.setting p
|
||||
*/
|
||||
td.domain-registrar-contacts {
|
||||
border-top: solid 0.5em white;
|
||||
}
|
17
java/google/registry/ui/css/contact-us.css
Normal file
|
@ -0,0 +1,17 @@
|
|||
#domain-registrar-contact-us
|
||||
.description * {
|
||||
color: #999 !important;
|
||||
line-height: 140%;
|
||||
}
|
||||
|
||||
#domain-registrar-contact-us p {
|
||||
margin-bottom: 1.5em;
|
||||
line-height: 140%;
|
||||
}
|
||||
|
||||
#registry-phone {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 1em;
|
||||
background-color: #eaeaea;
|
||||
}
|
63
java/google/registry/ui/css/dashboard.css
Normal file
|
@ -0,0 +1,63 @@
|
|||
#domain-registrar-dashboard {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#domain-registrar-dashboard p {
|
||||
color: grey;
|
||||
}
|
||||
|
||||
#domain-registrar-dashboard super {
|
||||
color: red;
|
||||
font-size: 0.5em;
|
||||
vertical-align: super;
|
||||
font-weight: bold;
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
#domain-registrar-dashboard table {
|
||||
border-collapse: collapse;
|
||||
margin: 3em auto;
|
||||
}
|
||||
|
||||
#domain-registrar-dashboard div.dashbox {
|
||||
width: 230px;
|
||||
min-width: 230px;
|
||||
height: 260px;
|
||||
padding: 1em 2em;
|
||||
margin: 0 1.5em;
|
||||
color: #777;
|
||||
border-radius: 10px;
|
||||
background-color: #f9f9f9;
|
||||
text-align: center;
|
||||
line-height: 140%;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
#domain-registrar-dashboard div.dashbox h2 {
|
||||
font-weight: bold;
|
||||
color: #555;
|
||||
margin: 2em auto 1em auto;
|
||||
}
|
||||
|
||||
#domain-registrar-dashboard div.dashbox a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
#domain-registrar-dashboard table img {
|
||||
display: block;
|
||||
margin: 1em auto;
|
||||
padding: 1em auto;
|
||||
}
|
||||
|
||||
#domain-registrar-dashboard table + p {
|
||||
margin: 2em auto;
|
||||
width: 75%;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#domain-registrar-dashboard p img {
|
||||
margin-right: 1em;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
}
|
1
java/google/registry/ui/css/demo_css.css
Normal file
61
java/google/registry/ui/css/epp.css
Normal file
|
@ -0,0 +1,61 @@
|
|||
/* EPP styles */
|
||||
|
||||
.reg-add:before {
|
||||
content: '+ ';
|
||||
}
|
||||
|
||||
.contact h1 {
|
||||
width: 650px;
|
||||
background: url('/assets/images/ic_contacts_blue_12.png') no-repeat 0 0.7em;
|
||||
padding-left: 2em;
|
||||
margin-left: 0.75em;
|
||||
}
|
||||
|
||||
/* Remove button. */
|
||||
#contact-postalInfo table button {
|
||||
margin: 1em;
|
||||
float: right;
|
||||
}
|
||||
|
||||
#contact-postalInfoHeader {
|
||||
color: #777;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#contact-postalInfo .info {
|
||||
font-size: 11px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
#contact-postalInfo table {
|
||||
margin: 0 0 1em 0;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
#contact-postalInfo tr {
|
||||
margin: auto 2em;
|
||||
}
|
||||
|
||||
#contact-postalInfo tr:first-child {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
#contact-postalInfo tr:last-child {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
#contact-postalInfo table,
|
||||
#contact-postalInfo input[readonly],
|
||||
#contact-postalInfo textarea[readonly] {
|
||||
background-color: #f1f1f1;
|
||||
border-color: #f1f1f1;
|
||||
}
|
||||
|
||||
#contact-postalInfoHeader button {
|
||||
display: block;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
#contact-postalInfoHeader button[disabled] {
|
||||
display: none;
|
||||
}
|
182
java/google/registry/ui/css/forms.css
Normal file
|
@ -0,0 +1,182 @@
|
|||
form.set,
|
||||
form.item,
|
||||
div.set,
|
||||
div.item {
|
||||
width: 700px;
|
||||
}
|
||||
|
||||
.set table,
|
||||
.item table {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
/* Going to play some games here to get section leads to float left,
|
||||
* followed by their next rows in floated right column. */
|
||||
.item table,
|
||||
.set table {
|
||||
padding-top: 2em;
|
||||
margin-top: 1em;
|
||||
border-top: solid 1px #ebebeb;
|
||||
}
|
||||
|
||||
.set tr,
|
||||
.item tr {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 28px;
|
||||
}
|
||||
|
||||
.set td,
|
||||
.item td {
|
||||
padding: 0.5em 0;
|
||||
}
|
||||
|
||||
.item tr.section-lead th h2,
|
||||
.item tr.section-lead th h3 {
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
color: #15c;
|
||||
}
|
||||
|
||||
tr.subsection h3::first-letter {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.set td:first-child,
|
||||
.item td:first-child {
|
||||
width: 260px;
|
||||
}
|
||||
|
||||
.set td:first-child span.description,
|
||||
.item td:first-child span.description {
|
||||
width: 170px;
|
||||
}
|
||||
|
||||
|
||||
/* Setting groups and labels. */
|
||||
|
||||
.set .kd-settings-pane-section td {
|
||||
border-bottom: solid 1px #ebebeb;
|
||||
}
|
||||
|
||||
td.setting-group-compact {
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
.setting-group-compact div {
|
||||
line-height: 140%;
|
||||
}
|
||||
|
||||
.item label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.set label + input {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.kd-settings-pane-section td label.setting-label {
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
|
||||
td.label {
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
/* Compact sequence of labels. */
|
||||
.setting label + label {
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
form label,
|
||||
.setting-label {
|
||||
line-height: 13px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.setting label {
|
||||
display: inline-block;
|
||||
font-weight: normal;
|
||||
margin-right: 1em;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
div.checkbox-with-label {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
|
||||
/* Controls */
|
||||
|
||||
.set input:not([type="submit"]),
|
||||
.item input:not([type="submit"]) {
|
||||
width: 250px;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.item input[type='checkbox'],
|
||||
.item input[type='radio'] {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
input[readonly] {
|
||||
border: solid 1px white;
|
||||
margin-left: 0.5em;
|
||||
padding-top: 0 !important;
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
|
||||
.setting input[type=radio] {
|
||||
}
|
||||
|
||||
.setting input[type=radio],
|
||||
.setting input[type=checkbox] {
|
||||
position: relative;
|
||||
top: -3px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consistent top space in second column items. Input elements need to
|
||||
* switch from using margin to padding when they're edit toggled, so that
|
||||
* the text doesn't move. The other elements are just defined here for
|
||||
* consistency.
|
||||
* @see td.domain-registrar-contacts
|
||||
*/
|
||||
input[readonly],
|
||||
td.setting p {
|
||||
margin-top: 0.5em;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.item button[disabled] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.checkbox-with-label input[type='checkbox']
|
||||
div.checkbox-with-label input[type='checkbox'] + label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/*
|
||||
* 3 line street has just inputs in a row
|
||||
* @see td padding-bottom
|
||||
*/
|
||||
.setting input + input,
|
||||
.setting div {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.setting div {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
.setting input[type='checkbox'] {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.setting input + button,
|
||||
.setting-item-list {
|
||||
margin-left: 0.5em;
|
||||
}
|
4737
java/google/registry/ui/css/kd_components.css
Normal file
10
java/google/registry/ui/css/registrar_imports_raw.css
Normal file
|
@ -0,0 +1,10 @@
|
|||
@import 'kd_components.css';
|
||||
@import 'registry.css';
|
||||
@import 'console.css';
|
||||
@import 'forms.css';
|
||||
@import 'epp.css';
|
||||
@import 'dashboard.css';
|
||||
@import 'resources.css';
|
||||
@import 'security-settings.css';
|
||||
@import 'contact-settings.css';
|
||||
@import 'contact-us.css';
|
306
java/google/registry/ui/css/registry.css
Normal file
|
@ -0,0 +1,306 @@
|
|||
body {
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden !important;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 13px;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
h1 {
|
||||
width: 100%;
|
||||
font-size: 1.5em;
|
||||
/* Bottom padding to get the paragraph text and Billing & resources to
|
||||
* line up. */
|
||||
padding: 0.75em 0.75em 3px 0;
|
||||
}
|
||||
|
||||
#kd-social a,
|
||||
#kd-social a:visited,
|
||||
#reg-content a,
|
||||
#reg-content a:visited {
|
||||
color: #15c;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: monospace;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
table {
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
/* Set some explicit form styles so there's no jumping during toggle
|
||||
* from readonly to editable. */
|
||||
input, textarea {
|
||||
border-style: solid;
|
||||
border-color: lightgrey;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
input[readonly], textarea[readonly] {
|
||||
resize: none;
|
||||
border-color: white;
|
||||
}
|
||||
|
||||
textarea {
|
||||
margin: 1em 0;
|
||||
font-family: monospace;
|
||||
font-size: 13px;
|
||||
border: solid 1px #ddd;
|
||||
}
|
||||
|
||||
textarea[readonly] {
|
||||
background-color: #eaeaea;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: solid 1px #ebebeb;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#kd-googlebar {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
min-width: 985px;
|
||||
padding: 1em 2em;
|
||||
white-space: nowrap;
|
||||
height: 37px;
|
||||
}
|
||||
|
||||
#kd-googlebar a.logo,
|
||||
#kd-searchfield,
|
||||
#kd-searchbutton {
|
||||
position: static;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#kd-search {
|
||||
width: 470px;
|
||||
margin-top: 2px;
|
||||
padding-top: 0px;
|
||||
padding-left: 3em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#kd-search form {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
input#kd-searchfield,
|
||||
#kd-searchbutton {
|
||||
height: 29px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
input#kd-searchfield {
|
||||
width: 400px;
|
||||
font-size: 12pt;
|
||||
}
|
||||
|
||||
#kd-searchbutton {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
#kd-social {
|
||||
position: fixed;
|
||||
top: 1.25em;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#kd-social .kd-name {
|
||||
float: none;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.kd-name a:before {
|
||||
content: ' | ';
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* Logo */
|
||||
a.logo {
|
||||
vertical-align: top;
|
||||
font-size: 30px;
|
||||
font-weight: 300;
|
||||
font-family: "open sans", sans-serif;
|
||||
color: #63666a;
|
||||
}
|
||||
|
||||
a.logo * {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
a.logo img {
|
||||
/* Get the baseline of "Google" and "Registry" to line up. */
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
/* Reg prefix. */
|
||||
|
||||
.reg-user-id {
|
||||
display: block;
|
||||
height: 1.5em;
|
||||
padding: 0;
|
||||
margin-top: -10px;
|
||||
}
|
||||
|
||||
/* Misc. */
|
||||
|
||||
#loady {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: -30px;
|
||||
}
|
||||
|
||||
.eppResponse {
|
||||
color: #333;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.kd-appbar {
|
||||
padding: 1em 0;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
li.kd-menulistitem {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
#reg-nav.shown {
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
/* Begin fixed headers and nav selectors */
|
||||
#reg-app {
|
||||
float: left;
|
||||
margin-top: 64px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.kd-butterbar {
|
||||
position: absolute;
|
||||
display: block;
|
||||
margin-left: inherit;
|
||||
}
|
||||
|
||||
.kd-butterbar.shown {
|
||||
-webkit-transform: translateX(-50%);
|
||||
-ms-transform: translateX(-50%);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.kd-appbar {
|
||||
padding: 0.75em 0;
|
||||
}
|
||||
|
||||
#reg-app-buttons {
|
||||
/* Same as in reg-content below. Lines the left edge of the
|
||||
appbuttons and content area with the 'R' in Registry. */
|
||||
padding-left: 173px;
|
||||
}
|
||||
|
||||
.kd-content-sidebar {
|
||||
margin-left: 15px;
|
||||
padding-left: 0;
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
#reg-nav {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 128px;
|
||||
width: 155px;
|
||||
margin: 0 25px 0 0;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
#reg-navlist li {
|
||||
margin-left: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
#reg-navlist li ul {
|
||||
margin-left: 0em;
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
.reg-navlist-sub {
|
||||
padding-left: 3px;
|
||||
}
|
||||
|
||||
#reg-navlist a {
|
||||
margin-left: 0;
|
||||
padding-left: 2em;
|
||||
border-left: solid 3px white;
|
||||
}
|
||||
|
||||
#reg-navlist li ul,
|
||||
#reg-navlist a {
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
#reg-navlist li ul li {
|
||||
margin-left: -2em;
|
||||
}
|
||||
|
||||
#reg-navlist li ul li a {
|
||||
padding-left: 3em;
|
||||
}
|
||||
|
||||
#reg-navlist a.domain-active-nav {
|
||||
border-left: solid 3px red;
|
||||
font-weight: bold;
|
||||
color: #bf624B;
|
||||
}
|
||||
|
||||
#reg-content-and-footer {
|
||||
position: absolute;
|
||||
top: 105px;
|
||||
left: 173px;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 25px 0 1em 0;
|
||||
overflow-y: scroll !important;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
#reg-content {
|
||||
margin-bottom: 100px;
|
||||
}
|
||||
|
||||
#reg-content,
|
||||
.pageFooter {
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
#debug {
|
||||
position: absolute;
|
||||
top: 127px;
|
||||
right: 0;
|
||||
width: 15%;
|
||||
border-left: solid 1px grey;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
/* End fixed headers and nav selectors */
|
||||
|
||||
#reg-content,
|
||||
#reg-login {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.reg-select {
|
||||
margin-left: 23px;
|
||||
}
|
28
java/google/registry/ui/css/resources.css
Normal file
|
@ -0,0 +1,28 @@
|
|||
#domain-registrar-resources h2 {
|
||||
border-top: solid 1px #eee;
|
||||
padding-top: 1em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
#domain-registrar-resources h2 img {
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#domain-registrar-resources h2 + p,
|
||||
#domain-registrar-resources button,
|
||||
#domain-registrar-resources em {
|
||||
margin-left: 34px; /* Folder icon + ^^ 10px more */
|
||||
}
|
||||
|
||||
#domain-registrar-resources h2 + p {
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
#domain-registrar-resources a {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
#domain-registrar-resources em {
|
||||
color: red;
|
||||
}
|
67
java/google/registry/ui/css/security-settings.css
Normal file
|
@ -0,0 +1,67 @@
|
|||
/** Security Settings */
|
||||
#domain-registrar-phone-passcode,
|
||||
#domain-registrar-phone-passcode input {
|
||||
color: #3D9200;
|
||||
}
|
||||
|
||||
div#ips div.ip {
|
||||
width: 209px;
|
||||
}
|
||||
|
||||
#newIp {
|
||||
width: 187px;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
div#ips.editing div.ip input,
|
||||
div#ips.editing div.ip button[type=button] {
|
||||
display: inline-block;
|
||||
height: 27px;
|
||||
line-height: 27px;
|
||||
background: #ebebeb;
|
||||
vertical-align: top;
|
||||
border: none;
|
||||
border-bottom: solid 3px white;
|
||||
}
|
||||
|
||||
div#ips.editing div.ip input {
|
||||
width: 169px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: #555;
|
||||
padding-left: 5px ! important;
|
||||
}
|
||||
|
||||
div#ips.editing div.ip input[readonly] {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
div#ips.editing div.ip button[type=button] {
|
||||
float: right;
|
||||
margin-left: -2px;
|
||||
width: 30px;
|
||||
min-width: 30px;
|
||||
height: 30px;
|
||||
color: grey;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
div#ips.editing div.ip button[type=button]:hover {
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
div#ips.editing div.ip button[type=button] i {
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
div#ips.editing .kd-errormessage {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.item td.certificate h4 {
|
||||
margin-top: 1em;
|
||||
text-transform: lowercase;
|
||||
border: solid 1px red;
|
||||
}
|
14
java/google/registry/ui/epptmpl/contact_info.xml
Normal file
|
@ -0,0 +1,14 @@
|
|||
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||
<command>
|
||||
<info>
|
||||
<contact:info
|
||||
xmlns:contact="urn:ietf:params:xml:ns:contact-1.0">
|
||||
<contact:id>sh8013</contact:id>
|
||||
<contact:authInfo>
|
||||
<contact:pw>2fooBAR</contact:pw>
|
||||
</contact:authInfo>
|
||||
</contact:info>
|
||||
</info>
|
||||
<clTRID>ABC-12345</clTRID>
|
||||
</command>
|
||||
</epp>
|
14
java/google/registry/ui/epptmpl/contact_transfer_request.xml
Normal file
|
@ -0,0 +1,14 @@
|
|||
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||
<command>
|
||||
<transfer op="request">
|
||||
<contact:transfer
|
||||
xmlns:contact="urn:ietf:params:xml:ns:contact-1.0">
|
||||
<contact:id>sh8013</contact:id>
|
||||
<contact:authInfo>
|
||||
<contact:pw>2fooBAR</contact:pw>
|
||||
</contact:authInfo>
|
||||
</contact:transfer>
|
||||
</transfer>
|
||||
<clTRID>ABC-12345</clTRID>
|
||||
</command>
|
||||
</epp>
|
|
@ -0,0 +1,18 @@
|
|||
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||
<command>
|
||||
<create>
|
||||
<domain:create
|
||||
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
|
||||
<domain:name>example.tld</domain:name>
|
||||
<domain:period unit="y">2</domain:period>
|
||||
<domain:registrant>jd1234</domain:registrant>
|
||||
<domain:contact type="admin">sh8013</domain:contact>
|
||||
<domain:contact type="tech">sh8013</domain:contact>
|
||||
<domain:authInfo>
|
||||
<domain:pw>2fooBAR</domain:pw>
|
||||
</domain:authInfo>
|
||||
</domain:create>
|
||||
</create>
|
||||
<clTRID>ABC-12345</clTRID>
|
||||
</command>
|
||||
</epp>
|
11
java/google/registry/ui/epptmpl/domain_delete.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||
<command>
|
||||
<delete>
|
||||
<domain:delete
|
||||
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
|
||||
<domain:name>example.tld</domain:name>
|
||||
</domain:delete>
|
||||
</delete>
|
||||
<clTRID>ABC-12345</clTRID>
|
||||
</command>
|
||||
</epp>
|
|
@ -0,0 +1,17 @@
|
|||
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||
<command>
|
||||
<update>
|
||||
<domain:update
|
||||
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
|
||||
<domain:name>example.tld</domain:name>
|
||||
<domain:chg/>
|
||||
</domain:update>
|
||||
</update>
|
||||
<extension>
|
||||
<rgp:update xmlns:rgp="urn:ietf:params:xml:ns:rgp-1.0">
|
||||
<rgp:restore op="request"/>
|
||||
</rgp:update>
|
||||
</extension>
|
||||
<clTRID>ABC-12345</clTRID>
|
||||
</command>
|
||||
</epp>
|
4
java/google/registry/ui/epptmpl/hello.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||
<hello/>
|
||||
</epp>
|
18
java/google/registry/ui/epptmpl/login_valid.xml
Normal file
|
@ -0,0 +1,18 @@
|
|||
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||
<command>
|
||||
<login>
|
||||
<clID>NewRegistrar</clID>
|
||||
<pw>foo-BAR2</pw>
|
||||
<options>
|
||||
<version>1.0</version>
|
||||
<lang>en</lang>
|
||||
</options>
|
||||
<svcs>
|
||||
<objURI>urn:ietf:params:xml:ns:host-1.0</objURI>
|
||||
<objURI>urn:ietf:params:xml:ns:domain-1.0</objURI>
|
||||
<objURI>urn:ietf:params:xml:ns:contact-1.0</objURI>
|
||||
</svcs>
|
||||
</login>
|
||||
<clTRID>ABC-12345</clTRID>
|
||||
</command>
|
||||
</epp>
|
6
java/google/registry/ui/epptmpl/logout.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||
<command>
|
||||
<logout/>
|
||||
<clTRID>ABC-12345</clTRID>
|
||||
</command>
|
||||
</epp>
|
6
java/google/registry/ui/epptmpl/poll.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||
<command>
|
||||
<poll op="req"/>
|
||||
<clTRID>ABC-12345</clTRID>
|
||||
</command>
|
||||
</epp>
|
12
java/google/registry/ui/externs/BUILD
Normal file
|
@ -0,0 +1,12 @@
|
|||
package(default_visibility = ["//java/com/google/domain/registry:registry_project"])
|
||||
|
||||
|
||||
filegroup(
|
||||
name = "externs",
|
||||
srcs = glob(["*.js"]),
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "js_files_recursive",
|
||||
srcs = glob(["*.js"]),
|
||||
)
|
450
java/google/registry/ui/externs/braintree.js
Normal file
|
@ -0,0 +1,450 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
/**
|
||||
* @fileoverview Braintree JS SDK v2 externs. This file tells the Closure
|
||||
* Compiler how Braintree's API is defined, which allows us to use it with type
|
||||
* safety and dot-notation.
|
||||
* @externs
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @type {!braintreepayments.Braintree}
|
||||
*/
|
||||
var braintree;
|
||||
|
||||
|
||||
/**
|
||||
* Fake namespace for pure Closure Compiler types not defined by the SDK.
|
||||
*/
|
||||
var braintreepayments = {};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
braintreepayments.Braintree = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} clientTokenFromServer
|
||||
* @param {string} integrationType Either 'dropin' or 'custom'.
|
||||
* @param {{container: (string|!Element|undefined),
|
||||
* dataCollector: (!Object|undefined),
|
||||
* enableCORS: (boolean|undefined),
|
||||
* form: (string|undefined),
|
||||
* hostedFields: (!Object|undefined),
|
||||
* id: (string|undefined),
|
||||
* onError: (function(!braintreepayments.Error)|undefined),
|
||||
* onPaymentMethodReceived:
|
||||
* (function(!braintreepayments.PaymentMethod)|undefined),
|
||||
* onReady: (function(!braintreepayments.Integrator)|undefined),
|
||||
* paypal: (undefined|{
|
||||
* amount: (number|undefined),
|
||||
* container: (string|!Element),
|
||||
* currency: (string|undefined),
|
||||
* displayName: (string|undefined),
|
||||
* enableBillingAddress: (boolean|undefined),
|
||||
* enableShippingAddress: (boolean|undefined),
|
||||
* headless: (boolean|undefined),
|
||||
* locale: (string|undefined),
|
||||
* onCancelled: (function()|undefined),
|
||||
* onPaymentMethodReceived:
|
||||
* (function(!braintreepayments.PaymentMethod)|undefined),
|
||||
* onUnsupported: (function()|undefined),
|
||||
* paymentMethodNonceInputField: (string|!Element|undefined),
|
||||
* shippingAddressOverride: (undefined|{
|
||||
* recipientName: string,
|
||||
* streetAddress: string,
|
||||
* extendedAddress: (string|undefined),
|
||||
* locality: string,
|
||||
* countryCodeAlpha2: string,
|
||||
* postalCode: string,
|
||||
* region: string,
|
||||
* phone: (string|undefined),
|
||||
* editable: boolean
|
||||
* }),
|
||||
* singleUse: (boolean|undefined)
|
||||
* })
|
||||
* }} options
|
||||
* @see https://developers.braintreepayments.com/guides/client-sdk/javascript/v2#global-setup
|
||||
*/
|
||||
braintreepayments.Braintree.prototype.setup =
|
||||
function(clientTokenFromServer, integrationType, options) {};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @final
|
||||
* @see https://developers.braintreepayments.com/guides/drop-in/javascript/v2#onerror
|
||||
*/
|
||||
braintreepayments.Error = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Describes type of error that occurred (e.g. "CONFIGURATION", "VALIDATION".)
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.Error.prototype.type;
|
||||
|
||||
|
||||
/**
|
||||
* Human-readable string describing the error.
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.Error.prototype.message;
|
||||
|
||||
|
||||
/**
|
||||
* @type {(!braintreepayments.ErrorDetails|undefined)}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.Error.prototype.details;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
braintreepayments.ErrorDetails = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* @type {(!Array<!braintreepayments.ErrorField>|undefined)}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.ErrorDetails.prototype.invalidFields;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
braintreepayments.ErrorField = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Field which failed validation. It will match one of the following: "number",
|
||||
* "cvv", "expiration", or "postalCode".
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.ErrorField.prototype.fieldKey;
|
||||
|
||||
|
||||
/**
|
||||
* This will be {@code true} if the associated input is empty.
|
||||
* @type {(boolean|undefined)}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.ErrorField.prototype.isEmpty;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
braintreepayments.Integrator = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.Integrator.prototype.deviceData;
|
||||
|
||||
|
||||
/**
|
||||
* @type {!braintreepayments.PaypalIntegrator}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.Integrator.prototype.paypal;
|
||||
|
||||
|
||||
/**
|
||||
* @param {function()=} opt_callback
|
||||
* @see https://developers.braintreepayments.com/guides/client-sdk/javascript/v2#teardown
|
||||
*/
|
||||
braintreepayments.Integrator.prototype.teardown = function(opt_callback) {};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
braintreepayments.PaypalIntegrator = function() {};
|
||||
|
||||
|
||||
braintreepayments.PaypalIntegrator.prototype.closeAuthFlow = function() {};
|
||||
|
||||
|
||||
braintreepayments.PaypalIntegrator.prototype.initAuthFlow = function() {};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
braintreepayments.PaymentMethod = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaymentMethod.prototype.nonce;
|
||||
|
||||
|
||||
/**
|
||||
* Either 'CreditCard' or 'PayPalAccount'.
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaymentMethod.prototype.type;
|
||||
|
||||
|
||||
/**
|
||||
* @type {(!braintreepayments.PaymentMethodDetailsCard|
|
||||
* !braintreepayments.PaymentMethodDetailsPaypal)}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaymentMethod.prototype.details;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @final
|
||||
* @see https://developers.braintreepayments.com/guides/client-sdk/javascript/v2#payment-method-details
|
||||
*/
|
||||
braintreepayments.PaymentMethodDetailsCard = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Can be 'Visa', 'MasterCard', 'Discover', 'Amex', or 'JCB'.
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaymentMethodDetailsCard.prototype.cardType;
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaymentMethodDetailsCard.prototype.lastTwo;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @final
|
||||
* @see https://developers.braintreepayments.com/guides/paypal/client-side/javascript/v2#options
|
||||
*/
|
||||
braintreepayments.PaymentMethodDetailsPaypal = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaymentMethodDetailsPaypal.prototype.email;
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaymentMethodDetailsPaypal.prototype.firstName;
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaymentMethodDetailsPaypal.prototype.lastName;
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaymentMethodDetailsPaypal.prototype.phone;
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaymentMethodDetailsPaypal.prototype.payerID;
|
||||
|
||||
|
||||
/**
|
||||
* @type {!braintreepayments.PaypalShippingAddress}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaymentMethodDetailsPaypal.prototype.shippingAddress;
|
||||
|
||||
|
||||
/**
|
||||
* @type {(!braintreepayments.PaypalBillingAddress|undefined)}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaymentMethodDetailsPaypal.prototype.billingAddress;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
braintreepayments.PaypalShippingAddress = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaypalShippingAddress.prototype.id;
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaypalShippingAddress.prototype.type;
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaypalShippingAddress.prototype.recipientName;
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaypalShippingAddress.prototype.streetAddress;
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaypalShippingAddress.prototype.extendedAddress;
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaypalShippingAddress.prototype.locality;
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaypalShippingAddress.prototype.region;
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaypalShippingAddress.prototype.postalCode;
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaypalShippingAddress.prototype.countryCodeAlpha;
|
||||
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaypalShippingAddress.prototype.defaultAddress;
|
||||
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaypalShippingAddress.prototype.preferredAddress;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
braintreepayments.PaypalBillingAddress = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaypalBillingAddress.prototype.streetAddress;
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaypalBillingAddress.prototype.extendedAddress;
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaypalBillingAddress.prototype.locality;
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaypalBillingAddress.prototype.region;
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaypalBillingAddress.prototype.postalCode;
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
braintreepayments.PaypalBillingAddress.prototype.countryCodeAlpha2;
|
116
java/google/registry/ui/externs/json.js
Normal file
|
@ -0,0 +1,116 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
/**
|
||||
* @fileoverview External JSON definitions. The purpose of this file is to give
|
||||
* type information to the JavaScript compiler so it won't rename these
|
||||
* properties.
|
||||
* @externs
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @suppress {duplicate}
|
||||
*/
|
||||
var registry = {};
|
||||
|
||||
|
||||
/**
|
||||
* @suppress {duplicate}
|
||||
*/
|
||||
registry.json = {};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @template T
|
||||
*/
|
||||
registry.json.Response = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Request state which can be {@code SUCCESS} or {@code ERROR}.
|
||||
* @type {string}
|
||||
*/
|
||||
registry.json.Response.prototype.status;
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
registry.json.Response.prototype.message;
|
||||
|
||||
|
||||
/**
|
||||
* @type {string|undefined}
|
||||
*/
|
||||
registry.json.Response.prototype.field;
|
||||
|
||||
|
||||
/**
|
||||
* @type {!Array.<T>}
|
||||
*/
|
||||
registry.json.Response.prototype.results;
|
||||
|
||||
|
||||
// XXX: Might not need undefineds here.
|
||||
/**
|
||||
* @typedef {{
|
||||
* clientIdentifier: string,
|
||||
* clientCertificate: string?,
|
||||
* clientCertificateHash: string?,
|
||||
* failoverClientCertificate: string?,
|
||||
* failoverClientCertificateHash: string?,
|
||||
* driveFolderId: string?,
|
||||
* ianaIdentifier: (number?|undefined),
|
||||
* icannReferralEmail: string,
|
||||
* ipAddressWhitelist: !Array<string>,
|
||||
* emailAddress: string,
|
||||
* phonePasscode: (string?|undefined),
|
||||
* phoneNumber: (string?|undefined),
|
||||
* faxNumber: (string?|undefined),
|
||||
* localizedAddress: registry.json.RegistrarAddress,
|
||||
* whoisServer: (string?|undefined),
|
||||
* referralUrl: (string?|undefined),
|
||||
* contacts: !Array.<registry.json.RegistrarContact>
|
||||
* }}
|
||||
*/
|
||||
registry.json.Registrar;
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* street: !Array.<string>,
|
||||
* city: string,
|
||||
* state: (string?|undefined),
|
||||
* zip: (string?|undefined),
|
||||
* countryCode: string
|
||||
* }}
|
||||
*/
|
||||
registry.json.RegistrarAddress;
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* name: (string?|undefined),
|
||||
* emailAddress: string,
|
||||
* visibleInWhoisAsAdmin: boolean,
|
||||
* visibleInWhoisAsTech: boolean,
|
||||
* phoneNumber: (string?|undefined),
|
||||
* faxNumber: (string?|undefined),
|
||||
* types: (string?|undefined)
|
||||
* }}
|
||||
*/
|
||||
registry.json.RegistrarContact;
|
66
java/google/registry/ui/externs/rpc_payment.js
Normal file
|
@ -0,0 +1,66 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
/**
|
||||
* @fileoverview RegistrarPaymentAction JSON RPC definitions.
|
||||
* @externs
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @suppress {duplicate}
|
||||
*/
|
||||
var registry = {};
|
||||
|
||||
|
||||
/**
|
||||
* @suppress {duplicate}
|
||||
*/
|
||||
registry.rpc = {};
|
||||
registry.rpc.Payment = {};
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* currency: string,
|
||||
* amount: string,
|
||||
* paymentMethodNonce: string
|
||||
* }}
|
||||
*/
|
||||
registry.rpc.Payment.Request;
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {registry.json.Response.<!registry.rpc.Payment.Result>}
|
||||
*/
|
||||
registry.rpc.Payment.Response;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
registry.rpc.Payment.Result = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
registry.rpc.Payment.Result.prototype.id;
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
registry.rpc.Payment.Result.prototype.formattedAmount;
|
68
java/google/registry/ui/externs/rpc_paymentsetup.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
/**
|
||||
* @fileoverview RegistrarPaymentSetupAction JSON RPC definitions.
|
||||
* @externs
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @suppress {duplicate}
|
||||
*/
|
||||
var registry = {};
|
||||
|
||||
|
||||
/**
|
||||
* @suppress {duplicate}
|
||||
*/
|
||||
registry.rpc = {};
|
||||
registry.rpc.PaymentSetup = {};
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {Object}
|
||||
*/
|
||||
registry.rpc.PaymentSetup.Request;
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {registry.json.Response.<!registry.rpc.PaymentSetup.Result>}
|
||||
*/
|
||||
registry.rpc.PaymentSetup.Response;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
registry.rpc.PaymentSetup.Result = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
registry.rpc.PaymentSetup.Result.prototype.token;
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
registry.rpc.PaymentSetup.Result.prototype.brainframe;
|
||||
|
||||
|
||||
/**
|
||||
* @type {!Array.<string>}
|
||||
*/
|
||||
registry.rpc.PaymentSetup.Result.prototype.currencies;
|
14
java/google/registry/ui/forms/BUILD
Normal file
|
@ -0,0 +1,14 @@
|
|||
package(default_visibility = ["//java/com/google/domain/registry:registry_project"])
|
||||
|
||||
|
||||
java_library(
|
||||
name = "forms",
|
||||
srcs = glob(["*.java"]),
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//java/com/google/common/annotations",
|
||||
"//java/com/google/common/base",
|
||||
"//java/com/google/common/collect",
|
||||
"//third_party/java/jsr305_annotations",
|
||||
],
|
||||
)
|
58
java/google/registry/ui/forms/FormException.java
Normal file
|
@ -0,0 +1,58 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package com.google.domain.registry.ui.forms;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import javax.annotation.Detainted;
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
/**
|
||||
* Exception thrown when a form is invalid. Problems with a specific
|
||||
* form field should use {@link FormFieldException} instead.
|
||||
*
|
||||
* <p>You can safely throw {@code FormException} from within your form
|
||||
* validator, and the message will automatically be propagated to the
|
||||
* client form interface.
|
||||
*/
|
||||
@NotThreadSafe
|
||||
public class FormException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Creates a new {@link FormException}
|
||||
*
|
||||
* @param userMessage should be a friendly message that's safe to show to the user.
|
||||
*/
|
||||
public FormException(@Detainted String userMessage) {
|
||||
super(checkNotNull(userMessage, "userMessage"), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link FormException}
|
||||
*
|
||||
* @param userMessage should be a friendly message that's safe to show to the user.
|
||||
* @param cause the original cause of this exception. May be null.
|
||||
*/
|
||||
public FormException(@Detainted String userMessage, Throwable cause) {
|
||||
super(checkNotNull(userMessage, "userMessage"), cause);
|
||||
}
|
||||
|
||||
/** Returns an error message that's safe to display to the user. */
|
||||
@Override
|
||||
@Detainted
|
||||
public String getMessage() {
|
||||
return super.getMessage();
|
||||
}
|
||||
}
|
790
java/google/registry/ui/forms/FormField.java
Normal file
|
@ -0,0 +1,790 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package com.google.domain.registry.ui.forms;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import com.google.common.base.CharMatcher;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Functions;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.FluentIterable;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.annotation.Detainted;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.Tainted;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* Declarative functional fluent form field converter / validator.
|
||||
*
|
||||
* <p>This class is responsible for converting arbitrary data, sent to us by the web browser, into
|
||||
* validated data structures that the server-side code can use. For example:<pre>
|
||||
*
|
||||
* private enum Gender { MALE, FEMALE }
|
||||
*
|
||||
* private static final FormField<String, String> NAME_FIELD = FormField.named("name")
|
||||
* .matches("[a-z]+")
|
||||
* .range(atMost(16))
|
||||
* .required()
|
||||
* .build();
|
||||
*
|
||||
* private static final FormField<String, Gender> GENDER_FIELD = FormField.named("gender")
|
||||
* .asEnum(Gender.class)
|
||||
* .required()
|
||||
* .build();
|
||||
*
|
||||
* public Person makePerson(Map<String, String> params) {
|
||||
* Person.Builder person = new Person.Builder();
|
||||
* for (String name : NAME_FIELD.extract(params).asSet()) {
|
||||
* person.setName(name);
|
||||
* }
|
||||
* for (Gender name : GENDER_FIELD.extract(params).asSet()) {
|
||||
* person.setGender(name);
|
||||
* }
|
||||
* return person.build();
|
||||
* }</pre>
|
||||
*
|
||||
* <p>This class provides <b>full type-safety</b> <i>if and only if</i> you statically initialize
|
||||
* your FormField objects and write a unit test that causes the class to be loaded.
|
||||
*
|
||||
* <h3>Exception Handling</h3>
|
||||
*
|
||||
* <p>When values passed to {@link #convert} or {@link #extract} don't meet the contract,
|
||||
* {@link FormFieldException} will be thrown, which provides the field name and a short error
|
||||
* message that's safe to pass along to the client.
|
||||
*
|
||||
* <p>You can safely throw {@code FormFieldException} from within your validator functions, and
|
||||
* the field name will automatically be propagated into the exception object for you.
|
||||
*
|
||||
* <p>In situations when you're validating lists or maps, you'll end up with a hierarchical field
|
||||
* naming structure. For example, if you were validating a list of maps, an error generated by the
|
||||
* {@code bar} field of the fifth item in the {@code foo} field would have a fully-qualified field
|
||||
* name of: {@code foo[5][bar]}.
|
||||
*
|
||||
* <h3>Library Definitions</h3>
|
||||
*
|
||||
* <p>You should never assign a partially constructed {@code FormField.Builder} to a variable or
|
||||
* constant. Instead, you should use {@link #asBuilder()} or {@link #asBuilderNamed(String)}.
|
||||
*
|
||||
* <p>Here is an example of how you might go about defining library definitions:<pre>
|
||||
*
|
||||
* final class FormFields {
|
||||
* private static final FormField<String, String> COUNTRY_CODE =
|
||||
* FormField.named("countryCode")
|
||||
* .range(Range.singleton(2))
|
||||
* .uppercased()
|
||||
* .in(ImmutableSet.copyOf(Locale.getISOCountries()))
|
||||
* .build();
|
||||
* }
|
||||
*
|
||||
* final class Form {
|
||||
* private static final FormField<String, String> COUNTRY_CODE_FIELD =
|
||||
* FormFields.COUNTRY_CODE.asBuilder()
|
||||
* .required()
|
||||
* .build();
|
||||
* }</pre>
|
||||
*
|
||||
* @param <I> input value type
|
||||
* @param <O> output value type
|
||||
*/
|
||||
@Immutable
|
||||
public final class FormField<I, O> {
|
||||
|
||||
private final String name;
|
||||
private final Class<I> typeIn;
|
||||
private final Class<O> typeOut;
|
||||
private final Function<I, O> converter;
|
||||
|
||||
private FormField(String name, Class<I> typeIn, Class<O> typeOut, Function<I, O> converter) {
|
||||
this.name = name;
|
||||
this.typeIn = typeIn;
|
||||
this.typeOut = typeOut;
|
||||
this.converter = converter;
|
||||
}
|
||||
|
||||
/** Returns an optional string form field named {@code name}. */
|
||||
public static Builder<String, String> named(String name) {
|
||||
return named(name, String.class);
|
||||
}
|
||||
|
||||
/** Returns an optional form field named {@code name} with a specific {@code inputType}. */
|
||||
public static <T> Builder<T, T> named(String name, Class<T> typeIn) {
|
||||
checkArgument(!name.isEmpty());
|
||||
return new Builder<>(name, checkNotNull(typeIn), typeIn, Functions.<T>identity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a form field builder for validating JSON nested maps.
|
||||
*
|
||||
* <p>Here's an example of how you'd use this feature:
|
||||
*
|
||||
* <pre>
|
||||
* private static final FormField<String, String> REGISTRAR_NAME_FIELD =
|
||||
* FormField.named("name")
|
||||
* .emptyToNull()
|
||||
* .required()
|
||||
* .build();
|
||||
*
|
||||
* private static final FormField<Map<String, ?>, Registrar> REGISTRAR_FIELD =
|
||||
* FormField.mapNamed("registrar")
|
||||
* .transform(Registrar.class, new Function<Map<String, ?>, Registrar>() {
|
||||
* @Nullable
|
||||
* @Override
|
||||
* public Registrar apply(@Nullable Map<String, ?> params) {
|
||||
* Registrar.Builder builder = new Registrar.Builder();
|
||||
* for (String name : REGISTRAR_NAME_FIELD.extractUntyped(params).asSet()) {
|
||||
* builder.setName(name);
|
||||
* }
|
||||
* return builder.build();
|
||||
* }})
|
||||
* .build();</pre>
|
||||
*
|
||||
* <p>When a {@link FormFieldException} is thrown, it'll be propagated to create a fully-qualified
|
||||
* field name. For example, if the JSON input is <pre>{registrar: {name: ""}}</pre> then the
|
||||
* {@link FormFieldException#getFieldName() field name} will be {@code registrar.name}.
|
||||
*/
|
||||
public static Builder<Map<String, ?>, Map<String, ?>> mapNamed(String name) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<Map<String, ?>> typeIn = (Class<Map<String, ?>>) (Class<?>) Map.class;
|
||||
return named(name, typeIn);
|
||||
}
|
||||
|
||||
/** Returns the name of this field. */
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert and validate a raw user-supplied value.
|
||||
*
|
||||
* @throws FormFieldException if value does not meet expected contracts.
|
||||
*/
|
||||
@Detainted
|
||||
public Optional<O> convert(@Tainted @Nullable I value) {
|
||||
try {
|
||||
return Optional.fromNullable(converter.apply(value));
|
||||
} catch (FormFieldException e) {
|
||||
throw e.propagate(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert and validate a raw user-supplied value from a map.
|
||||
*
|
||||
* <p>This is the same as saying: {@code field.convert(valueMap.get(field.name())}
|
||||
*
|
||||
* @throws FormFieldException if value does not meet expected contracts.
|
||||
*/
|
||||
@Detainted
|
||||
public Optional<O> extract(@Tainted Map<String, I> valueMap) {
|
||||
return convert(valueMap.get(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert and validate a raw user-supplied value from an untyped JSON map.
|
||||
*
|
||||
* @throws FormFieldException if value is wrong type or does not meet expected contracts.
|
||||
*/
|
||||
@Detainted
|
||||
public Optional<O> extractUntyped(@Tainted Map<String, ?> jsonMap) {
|
||||
Object value = jsonMap.get(name);
|
||||
I castedValue;
|
||||
try {
|
||||
castedValue = typeIn.cast(value);
|
||||
} catch (ClassCastException e) {
|
||||
throw new FormFieldException(String.format("Type error: got: %s, expected: %s",
|
||||
value.getClass().getSimpleName(),
|
||||
typeIn.getSimpleName())).propagate(name);
|
||||
}
|
||||
return convert(castedValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a builder of this object, which can be used to further restrict validation.
|
||||
*
|
||||
* @see #asBuilderNamed(String)
|
||||
*/
|
||||
public Builder<I, O> asBuilder() {
|
||||
return new Builder<>(name, typeIn, typeOut, converter);
|
||||
}
|
||||
|
||||
/** Same as {@link #asBuilder()} but changes the field name. */
|
||||
public Builder<I, O> asBuilderNamed(String newName) {
|
||||
checkArgument(!newName.isEmpty());
|
||||
return new Builder<>(newName, typeIn, typeOut, converter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutable builder for {@link FormField}.
|
||||
*
|
||||
* @param <I> input value type
|
||||
* @param <O> output value type
|
||||
*/
|
||||
public static final class Builder<I, O> {
|
||||
private final String name;
|
||||
private final Class<I> typeIn;
|
||||
private final Class<O> typeOut;
|
||||
private Function<I, O> converter;
|
||||
|
||||
private Builder(String name, Class<I> typeIn, Class<O> typeOut, Function<I, O> converter) {
|
||||
this.name = name;
|
||||
this.typeIn = typeIn;
|
||||
this.typeOut = typeOut;
|
||||
this.converter = converter;
|
||||
}
|
||||
|
||||
/** Causes {@code defaultValue} to be substituted if value is {@code null}. */
|
||||
public Builder<I, O> withDefault(O defaultValue) {
|
||||
return transform(new DefaultFunction<>(checkNotNull(defaultValue)));
|
||||
}
|
||||
|
||||
/** Ensure value is not {@code null}. */
|
||||
public Builder<I, O> required() {
|
||||
@SuppressWarnings("unchecked")
|
||||
Function<O, O> requiredFunction = (Function<O, O>) REQUIRED_FUNCTION;
|
||||
return transform(requiredFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform empty values into {@code null}.
|
||||
*
|
||||
* @throws IllegalStateException if current output type is not a {@link CharSequence} or
|
||||
* {@link Collection}.
|
||||
*/
|
||||
public Builder<I, O> emptyToNull() {
|
||||
checkState(CharSequence.class.isAssignableFrom(typeOut)
|
||||
|| Collection.class.isAssignableFrom(typeOut));
|
||||
@SuppressWarnings("unchecked")
|
||||
Function<O, O> emptyToNullFunction = (Function<O, O>) EMPTY_TO_NULL_FUNCTION;
|
||||
return transform(emptyToNullFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify {@link String} input to remove whitespace around the sides.
|
||||
*
|
||||
* <p>{@code null} values are passed through.
|
||||
*
|
||||
* @throws IllegalStateException if current output type is not a String.
|
||||
*/
|
||||
public Builder<I, String> trimmed() {
|
||||
checkState(String.class.isAssignableFrom(typeOut));
|
||||
@SuppressWarnings("unchecked")
|
||||
Function<O, String> trimFunction = (Function<O, String>) TRIM_FUNCTION;
|
||||
return transform(String.class, trimFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify {@link String} input to be uppercase.
|
||||
*
|
||||
* <p>{@code null} values are passed through.
|
||||
*
|
||||
* @throws IllegalStateException if current output type is not a String.
|
||||
*/
|
||||
public Builder<I, String> uppercased() {
|
||||
checkState(String.class.isAssignableFrom(typeOut));
|
||||
@SuppressWarnings("unchecked")
|
||||
Function<O, String> funk = (Function<O, String>) UPPERCASE_FUNCTION;
|
||||
return transform(String.class, funk);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify {@link String} input to be lowercase.
|
||||
*
|
||||
* <p>{@code null} values are passed through.
|
||||
*
|
||||
* @throws IllegalStateException if current output type is not a String.
|
||||
*/
|
||||
public Builder<I, String> lowercased() {
|
||||
checkState(String.class.isAssignableFrom(typeOut));
|
||||
@SuppressWarnings("unchecked")
|
||||
Function<O, String> funk = (Function<O, String>) LOWERCASE_FUNCTION;
|
||||
return transform(String.class, funk);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure input matches {@code pattern}.
|
||||
*
|
||||
* <p>{@code null} values are passed through.
|
||||
*
|
||||
* @param pattern is used to validate the user input. It matches against the whole string, so
|
||||
* you don't need to use the ^$ characters.
|
||||
* @param errorMessage is a helpful error message, which should include an example. If this is
|
||||
* not provided, a default error message will be shown that includes the regexp pattern.
|
||||
* @throws IllegalStateException if current output type is not a {@link CharSequence}.
|
||||
* @see #matches(Pattern)
|
||||
*/
|
||||
public Builder<I, O> matches(Pattern pattern, @Nullable String errorMessage) {
|
||||
checkState(CharSequence.class.isAssignableFrom(typeOut));
|
||||
return transform(
|
||||
new MatchesFunction<O>(checkNotNull(pattern), Optional.fromNullable(errorMessage)));
|
||||
}
|
||||
|
||||
/** Alias for {@link #matches(Pattern, String) matches(pattern, null)} */
|
||||
public Builder<I, O> matches(Pattern pattern) {
|
||||
return matches(pattern, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all characters not in {@code matcher}.
|
||||
*
|
||||
* <p>{@code null} values are passed through.
|
||||
*
|
||||
* @param matcher indicates which characters are to be retained
|
||||
* @throws IllegalStateException if current output type is not a {@link CharSequence}
|
||||
*/
|
||||
public Builder<I, String> retains(CharMatcher matcher) {
|
||||
checkState(CharSequence.class.isAssignableFrom(typeOut));
|
||||
@SuppressWarnings("unchecked") // safe due to checkState call
|
||||
Function<O, String> function =
|
||||
(Function<O, String>) new RetainFunction(checkNotNull(matcher));
|
||||
return transform(String.class, function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforce value length/size/value is within {@code range}.
|
||||
*
|
||||
* <p>The following input value types are supported:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link CharSequence}: Length must be within {@code range}.
|
||||
* <li>{@link Collection}: Size must be within {@code range}.
|
||||
* <li>{@link Number}: Value must be within {@code range}.
|
||||
* </ul>
|
||||
*
|
||||
* <p>{@code null} values are passed through. Please note that setting a lower bound on your
|
||||
* range does not imply {@link #required()}, as range checking only applies to non-{@code null}
|
||||
* values.
|
||||
*
|
||||
* @throws IllegalStateException if current output type is not one of the above types.
|
||||
*/
|
||||
public Builder<I, O> range(Range<Integer> range) {
|
||||
checkState(CharSequence.class.isAssignableFrom(typeOut)
|
||||
|| Collection.class.isAssignableFrom(typeOut)
|
||||
|| Number.class.isAssignableFrom(typeOut));
|
||||
return transform(new RangeFunction<O>(checkNotNull(range)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforce value be a member of {@code values}.
|
||||
*
|
||||
* <p>{@code null} values are passed through.
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code values} is empty.
|
||||
*/
|
||||
public Builder<I, O> in(Set<O> values) {
|
||||
checkArgument(!values.isEmpty());
|
||||
return transform(new InFunction<>(values));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs arbitrary type transformation from {@code O} to {@code T}.
|
||||
*
|
||||
* <p>Your {@code transform} function is expected to pass-through {@code null} values as a
|
||||
* no-op, since it's up to {@link #required()} to block them. You might also want to consider
|
||||
* using a try block that rethrows exceptions as {@link FormFieldException}.
|
||||
*
|
||||
* <p>Here's an example of how you'd convert from String to Integer:
|
||||
*
|
||||
* <pre>
|
||||
* FormField.named("foo", String.class)
|
||||
* .transform(Integer.class, new Function<String, Integer>() {
|
||||
* @Nullable
|
||||
* @Override
|
||||
* public Integer apply(@Nullable String input) {
|
||||
* try {
|
||||
* return input != null ? Integer.parseInt(input) : null;
|
||||
* } catch (IllegalArgumentException e) {
|
||||
* throw new FormFieldException("Invalid number.", e);
|
||||
* }
|
||||
* }})
|
||||
* .build();</pre>
|
||||
*
|
||||
* @see #transform(Function)
|
||||
*/
|
||||
public <T> Builder<I, T> transform(Class<T> newType, Function<O, T> transform) {
|
||||
return new Builder<>(name, typeIn, checkNotNull(newType),
|
||||
Functions.compose(checkNotNull(transform), converter));
|
||||
}
|
||||
|
||||
/**
|
||||
* Manipulates values without changing type.
|
||||
*
|
||||
* <p>Please see {@link #transform(Class, Function)} for information about the contract to
|
||||
* which {@code transform} is expected to conform.
|
||||
*/
|
||||
public Builder<I, O> transform(Function<O, O> transform) {
|
||||
this.converter = Functions.compose(checkNotNull(transform), converter);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uppercases value and converts to an enum field of {@code enumClass}.
|
||||
*
|
||||
* <p>{@code null} values are passed through.
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code enumClass} is not an enum class.
|
||||
* @throws IllegalStateException if current output type is not a String.
|
||||
*/
|
||||
public <C extends Enum<C>> Builder<I, C> asEnum(Class<C> enumClass) {
|
||||
checkArgument(enumClass.isEnum());
|
||||
checkState(String.class.isAssignableFrom(typeOut));
|
||||
return transform(enumClass, new ToEnumFunction<O, C>(enumClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns this form field into something that processes lists.
|
||||
*
|
||||
* <p>The current object definition will be applied to each item in the list. If a
|
||||
* {@link FormFieldException} is thrown when processing an item, then its
|
||||
* {@link FormFieldException#getFieldName() fieldName} will be rewritten to include the index,
|
||||
* e.g. {@code name} becomes {@code name[0]}.
|
||||
*
|
||||
* <p>The outputted list will be an {@link ImmutableList}. This is not reflected in the generic
|
||||
* typing for the sake of brevity.
|
||||
*
|
||||
* <p>A {@code null} value for list will be passed through. List items that convert to
|
||||
* {@code null} will be discarded (since {@code ImmutableList} does not permit {@code null}
|
||||
* values).
|
||||
*/
|
||||
public Builder<List<I>, List<O>> asList() {
|
||||
@SuppressWarnings("unchecked") Class<List<I>> in = (Class<List<I>>) (Class<I>) List.class;
|
||||
@SuppressWarnings("unchecked") Class<List<O>> out = (Class<List<O>>) (Class<O>) List.class;
|
||||
return new Builder<>(name, in, out, new ToListFunction<>(build()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns this form field into a split string list that applies itself to each item.
|
||||
*
|
||||
* <p>The behavior of this method is counter-intuitive. It behaves similar to {@link #asList()}
|
||||
* in the sense that all transforms specified <i>before</i> this method will be applied to the
|
||||
* individual resulting list items.
|
||||
*
|
||||
* <p>For example, to turn a comma-delimited string into an enum list:<pre> {@code
|
||||
*
|
||||
* private static final FormField<String, List<State>> STATES_FIELD =
|
||||
* FormField.named("states")
|
||||
* .uppercased()
|
||||
* .asEnum(State.class)
|
||||
* .asList(Splitter.on(',').omitEmptyStrings().trimResults())
|
||||
* .build();}</pre>
|
||||
*
|
||||
* <p>You'll notice that the transforms specified before this method are applied to each list
|
||||
* item. However unlike {@link #asList()}, if an error is thrown on an individual item, then
|
||||
* {@link FormFieldException#getFieldName()} will <i>not</i> contain the index.
|
||||
*
|
||||
* @throws IllegalStateException If either the current input type isn't String.
|
||||
*/
|
||||
public Builder<String, List<O>> asList(Splitter splitter) {
|
||||
checkNotNull(splitter);
|
||||
checkState(String.class.isAssignableFrom(typeIn));
|
||||
@SuppressWarnings("unchecked") Class<List<O>> out = (Class<List<O>>) (Class<O>) List.class;
|
||||
@SuppressWarnings("unchecked") FormField<String, O> inField = (FormField<String, O>) build();
|
||||
return new Builder<>(name, String.class, out, new SplitToListFunction<>(inField, splitter));
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #asList()} but outputs an {@link ImmutableSet} instead.
|
||||
*
|
||||
* @throws IllegalStateException if you called asList() before calling this method.
|
||||
*/
|
||||
public Builder<List<I>, Set<O>> asSet() {
|
||||
checkState(!List.class.isAssignableFrom(typeOut));
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<Set<O>> setOut = (Class<Set<O>>) (Class<O>) Set.class;
|
||||
@SuppressWarnings("unchecked")
|
||||
Function<List<O>, Set<O>> toSetFunction =
|
||||
(Function<List<O>, Set<O>>) (Function<O, O>) TO_SET_FUNCTION;
|
||||
return asList().transform(setOut, toSetFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #asList(Splitter)} but outputs an {@link ImmutableSet} instead.
|
||||
*
|
||||
* @throws IllegalStateException If the current input type isn't String.
|
||||
*/
|
||||
public Builder<String, Set<O>> asSet(Splitter splitter) {
|
||||
checkNotNull(splitter);
|
||||
checkState(String.class.isAssignableFrom(typeIn));
|
||||
@SuppressWarnings("unchecked") Class<Set<O>> out = (Class<Set<O>>) (Class<O>) Set.class;
|
||||
@SuppressWarnings("unchecked") FormField<String, O> inField = (FormField<String, O>) build();
|
||||
return new Builder<>(name, String.class, out, new SplitToSetFunction<>(inField, splitter));
|
||||
}
|
||||
|
||||
/** Creates a new {@link FormField} instance. */
|
||||
public FormField<I, O> build() {
|
||||
return new FormField<>(name, typeIn, typeOut, converter);
|
||||
}
|
||||
|
||||
private static final Function<List<Object>, Set<Object>> TO_SET_FUNCTION =
|
||||
new Function<List<Object>, Set<Object>>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public Set<Object> apply(@Nullable List<Object> input) {
|
||||
return input != null ? ImmutableSet.copyOf(input) : null;
|
||||
}};
|
||||
|
||||
private static final Function<String, String> TRIM_FUNCTION =
|
||||
new Function<String, String>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public String apply(@Nullable String input) {
|
||||
return input != null ? input.trim() : null;
|
||||
}};
|
||||
|
||||
private static final Function<String, String> UPPERCASE_FUNCTION =
|
||||
new Function<String, String>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public String apply(@Nullable String input) {
|
||||
return input != null ? input.toUpperCase() : null;
|
||||
}};
|
||||
|
||||
private static final Function<String, String> LOWERCASE_FUNCTION =
|
||||
new Function<String, String>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public String apply(@Nullable String input) {
|
||||
return input != null ? input.toLowerCase() : null;
|
||||
}};
|
||||
|
||||
private static final Function<Object, Object> REQUIRED_FUNCTION =
|
||||
new Function<Object, Object>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public Object apply(@Nullable Object input) {
|
||||
if (input == null) {
|
||||
throw new FormFieldException("This field is required.");
|
||||
}
|
||||
return input;
|
||||
}};
|
||||
|
||||
private static final Function<Object, Object> EMPTY_TO_NULL_FUNCTION =
|
||||
new Function<Object, Object>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public Object apply(@Nullable Object input) {
|
||||
return input instanceof CharSequence && ((CharSequence) input).length() == 0
|
||||
|| input instanceof Collection && ((Collection<?>) input).isEmpty()
|
||||
? null : input;
|
||||
}};
|
||||
|
||||
private static final class DefaultFunction<O> implements Function<O, O> {
|
||||
private final O defaultValue;
|
||||
|
||||
DefaultFunction(O defaultValue) {
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public O apply(@Nullable O input) {
|
||||
return input != null ? input : defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class RangeFunction<O> implements Function<O, O> {
|
||||
private final Range<Integer> range;
|
||||
|
||||
RangeFunction(Range<Integer> range) {
|
||||
this.range = range;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public O apply(@Nullable O input) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
if (input instanceof CharSequence) {
|
||||
checkRangeContains(range, ((CharSequence) input).length(), "Number of characters");
|
||||
} else if (input instanceof Collection) {
|
||||
checkRangeContains(range, ((Collection<?>) input).size(), "Number of items");
|
||||
} else if (input instanceof Number) {
|
||||
checkRangeContains(range, ((Number) input).intValue(), "Value");
|
||||
} else {
|
||||
throw new AssertionError();
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
private void checkRangeContains(Range<Integer> range, int value, String message) {
|
||||
if (!range.contains(value)) {
|
||||
throw new FormFieldException(
|
||||
String.format("%s (%,d) not in range %s", message, value, range));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class InFunction<O> implements Function<O, O> {
|
||||
private final Set<O> values;
|
||||
|
||||
InFunction(Set<O> values) {
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public O apply(@Nullable O input) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
if (!values.contains(input)) {
|
||||
throw new FormFieldException("Unrecognized value.");
|
||||
}
|
||||
return input;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class MatchesFunction<O> implements Function<O, O> {
|
||||
private final Pattern pattern;
|
||||
private final Optional<String> errorMessage;
|
||||
|
||||
MatchesFunction(Pattern pattern, Optional<String> errorMessage) {
|
||||
this.pattern = pattern;
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public O apply(@Nullable O input) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
if (!pattern.matcher((CharSequence) input).matches()) {
|
||||
throw new FormFieldException(errorMessage.or("Must match pattern: " + pattern));
|
||||
}
|
||||
return input;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class RetainFunction implements Function<CharSequence, String> {
|
||||
private final CharMatcher matcher;
|
||||
|
||||
RetainFunction(CharMatcher matcher) {
|
||||
this.matcher = matcher;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String apply(@Nullable CharSequence input) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
return matcher.retainFrom(input);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ToEnumFunction<O, C extends Enum<C>> implements Function<O, C> {
|
||||
private final Class<C> enumClass;
|
||||
|
||||
ToEnumFunction(Class<C> enumClass) {
|
||||
this.enumClass = enumClass;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public C apply(@Nullable O input) {
|
||||
try {
|
||||
return input != null ? Enum.valueOf(enumClass, ((String) input).toUpperCase()) : null;
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new FormFieldException(
|
||||
String.format("Enum %s does not contain '%s'", enumClass.getSimpleName(), input));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ToListFunction<I, O> implements Function<List<I>, List<O>> {
|
||||
private final FormField<I, O> itemField;
|
||||
|
||||
ToListFunction(FormField<I, O> itemField) {
|
||||
this.itemField = itemField;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<O> apply(@Nullable List<I> input) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
ImmutableList.Builder<O> builder = new ImmutableList.Builder<>();
|
||||
for (int i = 0; i < input.size(); i++) {
|
||||
I inputItem = itemField.typeIn.cast(input.get(i));
|
||||
O outputItem;
|
||||
try {
|
||||
outputItem = itemField.converter.apply(inputItem);
|
||||
} catch (FormFieldException e) {
|
||||
throw e.propagate(i);
|
||||
}
|
||||
if (outputItem != null) {
|
||||
builder.add(outputItem);
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SplitToListFunction<O> implements Function<String, List<O>> {
|
||||
private final FormField<String, O> itemField;
|
||||
private final Splitter splitter;
|
||||
|
||||
SplitToListFunction(FormField<String, O> itemField, Splitter splitter) {
|
||||
this.itemField = itemField;
|
||||
this.splitter = splitter;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<O> apply(@Nullable String input) {
|
||||
return input == null ? null : FluentIterable
|
||||
.from(splitter.split(input))
|
||||
.transform(itemField.converter)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SplitToSetFunction<O> implements Function<String, Set<O>> {
|
||||
private final FormField<String, O> itemField;
|
||||
private final Splitter splitter;
|
||||
|
||||
SplitToSetFunction(FormField<String, O> itemField, Splitter splitter) {
|
||||
this.itemField = itemField;
|
||||
this.splitter = splitter;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Set<O> apply(@Nullable String input) {
|
||||
return input == null ? null : FluentIterable
|
||||
.from(splitter.split(input))
|
||||
.transform(itemField.converter)
|
||||
.toSet();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
176
java/google/registry/ui/forms/FormFieldException.java
Normal file
|
@ -0,0 +1,176 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package com.google.domain.registry.ui.forms;
|
||||
|
||||
import static com.google.common.base.MoreObjects.toStringHelper;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.annotation.CheckReturnValue;
|
||||
import javax.annotation.Detainted;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
/**
|
||||
* Exception thrown when a form field contains a bad value.
|
||||
*
|
||||
* <p>You can safely throw {@code FormFieldException} from within your validator functions, and
|
||||
* the field name will automatically be propagated into the exception object for you.
|
||||
*
|
||||
* <p>The way that field names work is a bit complicated, because we need to support complex nested
|
||||
* field names like {@code foo[3].bar}. So what happens is the original exception will be thrown by
|
||||
* a {@link FormField} validator without the field set. Then as the exception bubbles up the stack,
|
||||
* it'll be caught by the {@link FormField#convert(Object) convert} method, which then prepends the
|
||||
* name of that component. Then when the exception reaches the user, the {@link #getFieldName()}
|
||||
* method will produce the fully-qualified field name.
|
||||
*
|
||||
* <p>This propagation mechanism is also very important when writing
|
||||
* {@link FormField.Builder#transform(com.google.common.base.Function) transform} functions, which
|
||||
* oftentimes will not know the name of the field they're validating.
|
||||
*/
|
||||
@NotThreadSafe
|
||||
public final class FormFieldException extends FormException {
|
||||
|
||||
private final List<Object> names = new ArrayList<>();
|
||||
|
||||
@Nullable
|
||||
private String lazyFieldName;
|
||||
|
||||
/**
|
||||
* Creates a new {@link FormFieldException}
|
||||
*
|
||||
* <p>This exception should only be thrown from within a {@link FormField} converter function.
|
||||
* The field name will automatically be propagated into the exception object for you.
|
||||
*
|
||||
* @param userMessage should be a friendly message that's safe to show to the user.
|
||||
*/
|
||||
public FormFieldException(@Detainted String userMessage) {
|
||||
super(checkNotNull(userMessage, "userMessage"), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link FormFieldException}
|
||||
*
|
||||
* <p>This exception should only be thrown from within a {@link FormField} converter function.
|
||||
* The field name will automatically be propagated into the exception object for you.
|
||||
*
|
||||
* @param userMessage should be a friendly message that's safe to show to the user.
|
||||
* @param cause the original cause of this exception (non-null).
|
||||
*/
|
||||
public FormFieldException(@Detainted String userMessage, Throwable cause) {
|
||||
super(checkNotNull(userMessage, "userMessage"), checkNotNull(cause, "cause"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link FormFieldException} for a particular form field.
|
||||
*
|
||||
* <p>This exception should only be thrown from within a {@link FormField} MAP converter function
|
||||
* in situations where you're performing additional manual validation.
|
||||
*
|
||||
* @param userMessage should be a friendly message that's safe to show to the user.
|
||||
*/
|
||||
public FormFieldException(FormField<?, ?> field, @Detainted String userMessage) {
|
||||
this(field.name(), userMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link FormFieldException} for a particular field name.
|
||||
*
|
||||
* @param field name corresponding to a {@link FormField#name()}
|
||||
* @param userMessage friendly message that's safe to show to the user
|
||||
*/
|
||||
public FormFieldException(String field, @Detainted String userMessage) {
|
||||
super(checkNotNull(userMessage, "userMessage"), null);
|
||||
propagateImpl(field);
|
||||
}
|
||||
|
||||
/** Returns the fully-qualified name (JavaScript syntax) of the form field causing this error. */
|
||||
public String getFieldName() {
|
||||
String fieldName = lazyFieldName;
|
||||
if (fieldName == null) {
|
||||
lazyFieldName = fieldName = getFieldNameImpl();
|
||||
}
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
private String getFieldNameImpl() {
|
||||
checkState(!names.isEmpty(),
|
||||
"FormFieldException was thrown outside FormField infrastructure!");
|
||||
Iterator<Object> namesIterator = Lists.reverse(names).iterator();
|
||||
StringBuilder result = new StringBuilder((String) namesIterator.next());
|
||||
while (namesIterator.hasNext()) {
|
||||
Object name = namesIterator.next();
|
||||
if (name instanceof String) {
|
||||
result.append('.').append(name);
|
||||
} else if (name instanceof Integer) {
|
||||
result.append('[').append(name).append(']');
|
||||
} else {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/** Returns self with {@code name} prepended, for propagating exceptions up the stack. */
|
||||
@CheckReturnValue
|
||||
@VisibleForTesting
|
||||
public FormFieldException propagate(String name) {
|
||||
return propagateImpl(name);
|
||||
}
|
||||
|
||||
/** Returns self with {@code index} prepended, for propagating exceptions up the stack. */
|
||||
@CheckReturnValue
|
||||
FormFieldException propagate(int index) {
|
||||
return propagateImpl(index);
|
||||
}
|
||||
|
||||
/** Returns self with {@code name} prepended, for propagating exceptions up the stack. */
|
||||
private FormFieldException propagateImpl(Object name) {
|
||||
lazyFieldName = null;
|
||||
names.add(checkNotNull(name));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
return this == obj
|
||||
|| obj instanceof FormFieldException
|
||||
&& Objects.equals(getCause(), ((FormFieldException) obj).getCause())
|
||||
&& Objects.equals(getMessage(), ((FormFieldException) obj).getMessage())
|
||||
&& Objects.equals(names, ((FormFieldException) obj).names);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getCause(), getMessage(), getFieldName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toStringHelper(getClass())
|
||||
.add("fieldName", getFieldName())
|
||||
.add("message", getMessage())
|
||||
.add("cause", getCause())
|
||||
.toString();
|
||||
}
|
||||
}
|
126
java/google/registry/ui/forms/FormFields.java
Normal file
|
@ -0,0 +1,126 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package com.google.domain.registry.ui.forms;
|
||||
|
||||
import static com.google.common.collect.Range.atMost;
|
||||
import static com.google.common.collect.Range.closed;
|
||||
import static com.google.common.collect.Range.singleton;
|
||||
import static java.util.Locale.getISOCountries;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** Utility class of {@link FormField} objects for validating EPP related things. */
|
||||
public final class FormFields {
|
||||
|
||||
private static final Pattern WHITESPACE = Pattern.compile("[ \\t\\r\\n]+");
|
||||
private static final Function<String, String> COLLAPSE_WHITESPACE =
|
||||
new Function<String, String>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public String apply(@Nullable String input) {
|
||||
return input != null ? WHITESPACE.matcher(input).replaceAll(" ") : null;
|
||||
}};
|
||||
|
||||
/**
|
||||
* Form field that applies XML Schema Token cleanup to input.
|
||||
*
|
||||
* <p>This trims the input and collapses whitespace.
|
||||
*
|
||||
* @see "http://www.w3.org/TR/xmlschema11-2/#token"
|
||||
*/
|
||||
public static final FormField<String, String> XS_TOKEN = FormField.named("xsToken")
|
||||
.emptyToNull()
|
||||
.trimmed()
|
||||
.transform(COLLAPSE_WHITESPACE)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Form field that ensures input does not contain tabs, line feeds, or carriage returns.
|
||||
*
|
||||
* @see "http://www.w3.org/TR/xmlschema11-2/#normalizedString"
|
||||
*/
|
||||
public static final FormField<String, String> XS_NORMALIZED_STRING =
|
||||
FormField.named("xsNormalizedString")
|
||||
.emptyToNull()
|
||||
.matches(Pattern.compile("[^\\t\\r\\n]*"), "Must not contain tabs or multiple lines.")
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Form field for +E164 phone numbers with a dot after the country prefix.
|
||||
*
|
||||
* @see "http://tools.ietf.org/html/rfc5733#section-4"
|
||||
*/
|
||||
public static final FormField<String, String> PHONE_NUMBER =
|
||||
XS_TOKEN.asBuilderNamed("phoneNumber")
|
||||
.range(atMost(17))
|
||||
.matches(Pattern.compile("(\\+[0-9]{1,3}\\.[0-9]{1,14})?"),
|
||||
"Must be a valid +E.164 phone number, e.g. +1.2125650000")
|
||||
.build();
|
||||
|
||||
/** Form field for EPP client identifiers. */
|
||||
public static final FormField<String, String> CLID = XS_TOKEN.asBuilderNamed("clid")
|
||||
.range(closed(3, 16))
|
||||
.build();
|
||||
|
||||
/** Form field for passwords (see pwType in epp.xsd). */
|
||||
public static final FormField<String, String> PASSWORD = XS_TOKEN.asBuilderNamed("password")
|
||||
.range(closed(6, 16))
|
||||
.build();
|
||||
|
||||
/** Form field for non-empty tokens (see minToken in eppcom.xsd). */
|
||||
public static final FormField<String, String> MIN_TOKEN = XS_TOKEN.asBuilderNamed("minToken")
|
||||
.emptyToNull()
|
||||
.build();
|
||||
|
||||
/** Form field for nameType (see rde-registrar/notification). */
|
||||
public static final FormField<String, String> NAME = XS_NORMALIZED_STRING.asBuilderNamed("name")
|
||||
.range(closed(1, 255))
|
||||
.build();
|
||||
|
||||
/** Form field for {@code labelType} from {@code eppcom.xsd}. */
|
||||
public static final FormField<String, String> LABEL = XS_TOKEN.asBuilderNamed("label")
|
||||
.range(closed(1, 255))
|
||||
.build();
|
||||
|
||||
/** Email address form field. */
|
||||
public static final FormField<String, String> EMAIL = XS_TOKEN.asBuilderNamed("email")
|
||||
.matches(Pattern.compile("[^@]+@[^@.]+\\.[^@]+"), "Please enter a valid email address.")
|
||||
.build();
|
||||
|
||||
/** Two-letter ISO country code form field. */
|
||||
public static final FormField<String, String> COUNTRY_CODE =
|
||||
XS_TOKEN.asBuilderNamed("countryCode")
|
||||
.range(singleton(2))
|
||||
.uppercased()
|
||||
.in(ImmutableSet.copyOf(getISOCountries()))
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Ensure value is an EPP Repository Object IDentifier (ROID).
|
||||
*
|
||||
* @see "http://tools.ietf.org/html/rfc5730#section-4.2"
|
||||
*/
|
||||
public static final FormField<String, String> ROID = XS_TOKEN.asBuilderNamed("roid")
|
||||
.matches(Pattern.compile("(\\w|_){1,80}-\\w{1,8}"),
|
||||
"Please enter a valid EPP ROID, e.g. SH8013-REP")
|
||||
.build();
|
||||
|
||||
private FormFields() {}
|
||||
}
|
17
java/google/registry/ui/forms/package-info.java
Normal file
|
@ -0,0 +1,17 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
/** Web application backend form processing utilities. */
|
||||
@javax.annotation.ParametersAreNonnullByDefault
|
||||
package com.google.domain.registry.ui.forms;
|
5
java/google/registry/ui/globals.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
DEBUG = null
|
||||
com.google.domain.registry.ui.ConsoleDebug.PRODUCTION = 0
|
||||
com.google.domain.registry.ui.ConsoleDebug.DEBUG = 1
|
||||
com.google.domain.registry.ui.ConsoleDebug.RAW = 2
|
||||
com.google.domain.registry.ui.ConsoleDebug.TEST = 3
|
7
java/google/registry/ui/html/BUILD
Normal file
|
@ -0,0 +1,7 @@
|
|||
package(default_visibility = ["//java/com/google/domain/registry:registry_project"])
|
||||
|
||||
|
||||
filegroup(
|
||||
name = "html_files",
|
||||
srcs = glob(["*.html"]),
|
||||
)
|
15
java/google/registry/ui/html/error.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>Server Error</title>
|
||||
<style>
|
||||
*{margin:0;padding:0}
|
||||
html,code{font:15px/22px arial,sans-serif}
|
||||
html{background:#fff;color:#222;padding:15px}
|
||||
body{margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px}
|
||||
* > body{background:url(/manager/img/ui/robot.png) 100% 5px no-repeat;padding-right:205px}
|
||||
p{margin:11px 0 22px;overflow:hidden}ins{color:#777;text-decoration:none}
|
||||
a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}
|
||||
</style>
|
||||
<a href="/"><img src="/assets/images/logo_sm.gif" alt=Google></a>
|
||||
<p><b>500.</b> <ins>That's an error.</ins>
|
||||
<p>Sorry, but the server encountered an error while processing your request.
|
6
java/google/registry/ui/html/index.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
<!doctype html>
|
||||
<meta http-equiv="refresh" content="0;URL=/registrar">
|
||||
<title>Domain Registry</title>
|
||||
<body>
|
||||
If this page doesn't change automatically, please go
|
||||
to <a href="/registrar">http://registry.google.com/registrar</a>
|
19
java/google/registry/ui/js/BUILD
Normal file
|
@ -0,0 +1,19 @@
|
|||
package(default_visibility = ["//java/com/google/domain/registry:registry_project"])
|
||||
|
||||
load("//third_party/closure/compiler:closure_js_library.bzl", "closure_js_library")
|
||||
|
||||
|
||||
filegroup(
|
||||
name = "js_files",
|
||||
srcs = glob(["*.js"]),
|
||||
)
|
||||
|
||||
closure_js_library(
|
||||
name = "js",
|
||||
srcs = [":js_files"],
|
||||
deps = [
|
||||
"//java/com/google/domain/registry/ui/soy:Console",
|
||||
"//java/com/google/domain/registry/ui/soy:Forms",
|
||||
"//javascript/closure",
|
||||
],
|
||||
)
|
210
java/google/registry/ui/js/component.js
Normal file
|
@ -0,0 +1,210 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
goog.provide('registry.Component');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.dom.classlist');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventHandler');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('registry.soy.forms');
|
||||
goog.require('registry.util');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Base component for UI.
|
||||
*
|
||||
* <pre>
|
||||
* ui/js/component.js - Base UI class.
|
||||
* ^
|
||||
* |
|
||||
* edit_item.js - Common controls for editable items.
|
||||
* ^
|
||||
* \
|
||||
* |-ui/js/resource_component.js - JSON resources
|
||||
* | ^
|
||||
* | \
|
||||
* | |- ui/js/registrar/settings.js
|
||||
* |
|
||||
* |-ui/js/registrar/xml_resource_component.js - EPP resources
|
||||
* ^
|
||||
* \
|
||||
* |- ui/js/registrar/contact.js
|
||||
* |- ui/js/registrar/domain.js
|
||||
* |- ui/js/registrar/host.js
|
||||
* </pre>
|
||||
*
|
||||
* @param {!registry.Console} cons the console singleton.
|
||||
* @constructor
|
||||
* @extends {goog.events.EventHandler}
|
||||
*/
|
||||
registry.Component = function(cons) {
|
||||
registry.Component.base(this, 'constructor');
|
||||
|
||||
/** @type {!registry.Console} */
|
||||
this.console = cons;
|
||||
|
||||
/**
|
||||
* The hashPath this component is mapped by. This is set by the
|
||||
* console after construction.
|
||||
* @type {string}
|
||||
*/
|
||||
this.basePath = '';
|
||||
|
||||
/**
|
||||
* Bean counter that's used by {@code addRemBtnHandlers},
|
||||
* e.g. {@code typeCounts['host']++} when user adds or removes.
|
||||
* @type {!Object.<string, number>}
|
||||
* @protected
|
||||
*/
|
||||
this.typeCounts = {};
|
||||
|
||||
/**
|
||||
* Stateful UI/server session model.
|
||||
* @type {?Object.<string, ?>}
|
||||
*/
|
||||
this.model = null;
|
||||
};
|
||||
goog.inherits(registry.Component, goog.events.EventHandler);
|
||||
|
||||
|
||||
/**
|
||||
* Subclasses shold override this to implement panel display.
|
||||
* @param {string} id The target resource id.
|
||||
*/
|
||||
registry.Component.prototype.bindToDom = function(id) {
|
||||
registry.util.unbutter();
|
||||
};
|
||||
|
||||
|
||||
// XXX: Should clean up the many remove button handler setup funcs.
|
||||
/**
|
||||
* Shared functionality for contact and host add button click events
|
||||
* to add form elements for inputing a new item name. Requires the
|
||||
* template to have:
|
||||
*
|
||||
* <ul>
|
||||
* <li>an add button with id="domain-[type]-add-button".
|
||||
* <li>a table row with id="domain-[type]s-footer".
|
||||
* </ul>
|
||||
*
|
||||
* @param {string} type e.g. 'contact', 'host'.
|
||||
* @param {function(): string} newFieldNameFn generates new form field's name.
|
||||
* @param {function(string): (?Element)=} opt_onAddFn currying further setup.
|
||||
* @param {function()=} opt_tmpl input element template.
|
||||
* @param {!Object=} opt_tmplArgs input element template parameter object.
|
||||
* @param {boolean=} opt_disable optionally disable the add button.
|
||||
* @protected
|
||||
*/
|
||||
registry.Component.prototype.addRemBtnHandlers = function(
|
||||
type,
|
||||
newFieldNameFn,
|
||||
opt_onAddFn,
|
||||
opt_tmpl,
|
||||
opt_tmplArgs,
|
||||
opt_disable) {
|
||||
var addBtnId = 'domain-' + type + '-add-button';
|
||||
var addBtn = goog.dom.getRequiredElement(addBtnId);
|
||||
if (!opt_disable) {
|
||||
addBtn.removeAttribute('disabled');
|
||||
}
|
||||
var addButtonClickCallback = function() {
|
||||
var fieldElts = [];
|
||||
var newFieldName = newFieldNameFn();
|
||||
var tmpl =
|
||||
opt_tmpl ? opt_tmpl : registry.soy.forms.inputFieldRow;
|
||||
var tmplArgs = opt_tmplArgs ? opt_tmplArgs : {
|
||||
label: 'New ' + type,
|
||||
name: newFieldName + '.value'
|
||||
};
|
||||
var newFieldInputRow = registry.util.renderBeforeRow(
|
||||
'domain-' + type + 's-footer', tmpl, tmplArgs);
|
||||
fieldElts.push(newFieldInputRow);
|
||||
// Save the add/rem op type as a hidden elt for use by
|
||||
// determine EPP add/remove semantics in subclasses.
|
||||
// e.g. domain.js#saveItem()
|
||||
var opElt = goog.dom.createDom(goog.dom.TagName.INPUT, {
|
||||
'type': 'hidden',
|
||||
'name': newFieldName + '.op',
|
||||
'value': 'add'
|
||||
});
|
||||
newFieldInputRow.lastChild.appendChild(opElt);
|
||||
if (opt_onAddFn) {
|
||||
var elt = opt_onAddFn(newFieldName);
|
||||
if (elt) {
|
||||
fieldElts.push(elt);
|
||||
}
|
||||
}
|
||||
this.appendRemoveBtn(
|
||||
goog.dom.getLastElementChild(newFieldInputRow),
|
||||
fieldElts,
|
||||
goog.bind(function() {
|
||||
this.typeCounts[type]--;
|
||||
}, this));
|
||||
this.typeCounts[type]++;
|
||||
};
|
||||
goog.events.listen(goog.dom.getRequiredElement(addBtnId),
|
||||
goog.events.EventType.CLICK,
|
||||
goog.bind(addButtonClickCallback, this));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper for making an element removable.
|
||||
* @param {Element} parent The element to append the remove button to.
|
||||
* @param {(Array.<Element>|function())=} opt_eltsToRemove
|
||||
* Elements to remove when the button is clicked or Function to do
|
||||
* the removing for full customization.
|
||||
* @param {function()=} opt_cb callback will be called if no
|
||||
* customized function is given.
|
||||
*/
|
||||
registry.Component.prototype.appendRemoveBtn =
|
||||
function(parent, opt_eltsToRemove, opt_cb) {
|
||||
var rmBtn = goog.dom.createDom(goog.dom.TagName.BUTTON,
|
||||
goog.getCssName('kd-button'),
|
||||
'Remove');
|
||||
goog.dom.appendChild(parent, rmBtn);
|
||||
var clickCb;
|
||||
if (opt_eltsToRemove instanceof Function) {
|
||||
clickCb = opt_eltsToRemove;
|
||||
} else {
|
||||
var eltsToRemove = opt_eltsToRemove ? opt_eltsToRemove : [parent];
|
||||
clickCb = function() {
|
||||
for (var i = 0; i < eltsToRemove.length; i++) {
|
||||
goog.dom.removeNode(eltsToRemove[i]);
|
||||
}
|
||||
if (opt_cb) {
|
||||
opt_cb();
|
||||
}
|
||||
};
|
||||
}
|
||||
goog.events.listen(rmBtn, goog.events.EventType.CLICK, clickCb, true);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Bind the remove button action for the given container element.
|
||||
* @param {!Element} containerElt
|
||||
*/
|
||||
registry.Component.prototype.enableRemoveButton = function(containerElt) {
|
||||
var rmBtn = goog.dom.getElementByClass(
|
||||
goog.getCssName('remove'), containerElt);
|
||||
goog.dom.classlist.toggle(rmBtn, goog.getCssName('hidden'));
|
||||
goog.events.listen(rmBtn, goog.events.EventType.CLICK, function() {
|
||||
goog.dom.removeNode(goog.dom.getParentElement(rmBtn));
|
||||
});
|
||||
};
|
123
java/google/registry/ui/js/console.js
Normal file
|
@ -0,0 +1,123 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
goog.provide('registry.Console');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.History');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.events.KeyCodes');
|
||||
goog.require('goog.history.EventType');
|
||||
goog.require('registry.util');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Abstract console for both admin and registrar console UIs.
|
||||
* @param {?registry.Session} session server request session.
|
||||
* @constructor
|
||||
* @extends {goog.Disposable}
|
||||
*/
|
||||
registry.Console = function(session) {
|
||||
registry.Console.base(this, 'constructor');
|
||||
|
||||
/**
|
||||
* @type {!goog.History}
|
||||
* @protected
|
||||
*/
|
||||
this.history = new goog.History();
|
||||
goog.events.listen(
|
||||
this.history,
|
||||
goog.history.EventType.NAVIGATE,
|
||||
goog.bind(this.handleHashChange, this));
|
||||
|
||||
/**
|
||||
* @type {?registry.Session} The server session.
|
||||
*/
|
||||
this.session = session;
|
||||
|
||||
this.bindToDom();
|
||||
};
|
||||
goog.inherits(registry.Console, goog.Disposable);
|
||||
|
||||
|
||||
/**
|
||||
* Helper to setup permanent page elements.
|
||||
*/
|
||||
registry.Console.prototype.bindToDom = function() {
|
||||
registry.util.unbutter();
|
||||
goog.events.listen(goog.dom.getRequiredElement('kd-searchbutton'),
|
||||
goog.events.EventType.CLICK,
|
||||
goog.bind(this.onSearch_, this));
|
||||
goog.events.listen(goog.dom.getRequiredElement('kd-searchfield'),
|
||||
goog.events.EventType.KEYUP,
|
||||
goog.bind(this.onSearchFieldKeyUp_, this));
|
||||
goog.events.listen(
|
||||
goog.dom.getElementByClass(goog.getCssName('kd-butterbar-dismiss')),
|
||||
goog.events.EventType.CLICK,
|
||||
registry.util.unbutter);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Subclasses should override to visit the hash token given by
|
||||
* {@code goog.History.getToken()}.
|
||||
*/
|
||||
registry.Console.prototype.handleHashChange = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} resourcePath Resource description path.
|
||||
*/
|
||||
registry.Console.prototype.view = function(resourcePath) {
|
||||
this.history.setToken(resourcePath);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for search bar.
|
||||
* @private
|
||||
*/
|
||||
registry.Console.prototype.onSearch_ = function() {
|
||||
var qElt = goog.dom.getRequiredElement('kd-searchfield');
|
||||
if (qElt.getAttribute('disabled')) {
|
||||
return;
|
||||
}
|
||||
var query = qElt.value;
|
||||
if (query == '') {
|
||||
return;
|
||||
}
|
||||
// Filtering this value change event.
|
||||
qElt.setAttribute('disabled', true);
|
||||
qElt.value = '';
|
||||
this.view(query);
|
||||
qElt.removeAttribute('disabled');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for key press in the search input field.
|
||||
* @param {!goog.events.KeyEvent} e Key event to handle.
|
||||
* @return {boolean} Whether the event should be continued or cancelled.
|
||||
* @private
|
||||
*/
|
||||
registry.Console.prototype.onSearchFieldKeyUp_ = function(e) {
|
||||
if (e.keyCode == goog.events.KeyCodes.ENTER) {
|
||||
this.onSearch_();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
294
java/google/registry/ui/js/edit_item.js
Normal file
|
@ -0,0 +1,294 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
goog.provide('registry.EditItem');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.classlist');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.soy');
|
||||
goog.require('registry.Component');
|
||||
goog.require('registry.soy.console');
|
||||
goog.require('registry.util');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An editable item, with Edit and Save/Cancel buttons in the appbar.
|
||||
* @param {!registry.Console} cons
|
||||
* @param {function()} itemTmpl
|
||||
* @constructor
|
||||
* @extends {registry.Component}
|
||||
*/
|
||||
registry.EditItem = function(cons, itemTmpl) {
|
||||
registry.EditItem.base(this, 'constructor', cons);
|
||||
|
||||
/**
|
||||
* @type {!Function}
|
||||
*/
|
||||
this.itemTmpl = itemTmpl;
|
||||
|
||||
/**
|
||||
* Optional current target resource id.
|
||||
* @type {?string}
|
||||
*/
|
||||
this.id = null;
|
||||
|
||||
/**
|
||||
* Transitional id for next resource during create.
|
||||
* @type {?string}
|
||||
*/
|
||||
this.nextId = null;
|
||||
};
|
||||
goog.inherits(registry.EditItem, registry.Component);
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.EditItem.prototype.bindToDom = function(id) {
|
||||
registry.EditItem.base(this, 'bindToDom', id);
|
||||
this.id = id;
|
||||
this.nextId = null;
|
||||
this.setupAppbar();
|
||||
};
|
||||
|
||||
|
||||
/** Setup appbar save/edit buttons. */
|
||||
registry.EditItem.prototype.setupAppbar = function() {
|
||||
goog.soy.renderElement(goog.dom.getRequiredElement('reg-appbar'),
|
||||
registry.soy.console.appbarButtons);
|
||||
goog.events.listen(goog.dom.getRequiredElement('reg-app-btn-add'),
|
||||
goog.events.EventType.CLICK,
|
||||
goog.bind(this.add, this));
|
||||
goog.events.listen(goog.dom.getRequiredElement('reg-app-btn-edit'),
|
||||
goog.events.EventType.CLICK,
|
||||
goog.bind(this.edit, this));
|
||||
goog.events.listen(goog.dom.getRequiredElement('reg-app-btn-save'),
|
||||
goog.events.EventType.CLICK,
|
||||
goog.bind(this.save, this));
|
||||
goog.events.listen(goog.dom.getRequiredElement('reg-app-btn-cancel'),
|
||||
goog.events.EventType.CLICK,
|
||||
goog.bind(this.cancel, this));
|
||||
goog.events.listen(goog.dom.getRequiredElement('reg-app-btn-back'),
|
||||
goog.events.EventType.CLICK,
|
||||
goog.bind(this.back, this));
|
||||
if (this.id) {
|
||||
registry.util.setVisible('reg-app-btns-edit', true);
|
||||
} else {
|
||||
registry.util.setVisible('reg-app-btn-add', true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve item from server. Overrides should callback to {@code
|
||||
* #handleFetchItem(string, !Object)}.
|
||||
* @param {string} id item id.
|
||||
*/
|
||||
registry.EditItem.prototype.fetchItem = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Handle result decoding and display.
|
||||
* @param {string} id The requested ID/name, for error message.
|
||||
* @param {!Object} rsp The requested object.
|
||||
*/
|
||||
registry.EditItem.prototype.handleFetchItem = goog.abstractMethod;
|
||||
|
||||
|
||||
/** Subclasses should override to continue processing after fetch. */
|
||||
registry.EditItem.prototype.processItem = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Show the item.
|
||||
* @param {!Object} objArgs
|
||||
*/
|
||||
registry.EditItem.prototype.renderItem = function(objArgs) {
|
||||
goog.soy.renderElement(goog.dom.getRequiredElement('reg-content'),
|
||||
this.itemTmpl,
|
||||
objArgs);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} if the component is currently being edited.
|
||||
*/
|
||||
registry.EditItem.prototype.isEditing = function() {
|
||||
return goog.dom.classlist.contains(
|
||||
goog.dom.getElement('reg-app'), goog.getCssName('editing'));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Toggles the editing state of a component. This will first hide the
|
||||
* elements of the {@code shown} class, then adds the {@code editing}
|
||||
* style to the component, then shows the elements that had the {@code
|
||||
* hidden} class.
|
||||
*/
|
||||
registry.EditItem.prototype.toggleEdit = function() {
|
||||
// Toggle appbar buttons.
|
||||
var addBtn = goog.dom.getRequiredElement('reg-app-btn-add');
|
||||
var editBtns = goog.dom.getRequiredElement('reg-app-btns-edit');
|
||||
var saveBtns = goog.dom.getRequiredElement('reg-app-btns-save');
|
||||
var editing = goog.dom.classlist.contains(saveBtns,
|
||||
registry.util.cssShown);
|
||||
if (editing) {
|
||||
registry.util.setVisible(saveBtns, false);
|
||||
if (this.id) {
|
||||
registry.util.setVisible(editBtns, true);
|
||||
} else {
|
||||
registry.util.setVisible(addBtn, true);
|
||||
}
|
||||
} else {
|
||||
if (this.id) {
|
||||
registry.util.setVisible(editBtns, false);
|
||||
} else {
|
||||
registry.util.setVisible(addBtn, false);
|
||||
}
|
||||
registry.util.setVisible(saveBtns, true);
|
||||
}
|
||||
// Then page contents.
|
||||
var parentElt = goog.dom.getElement('reg-content');
|
||||
var shownCssName = goog.getCssName('shown');
|
||||
var hiddenCssName = goog.getCssName('hidden');
|
||||
var shown = goog.dom.getElementsByClass(shownCssName, parentElt);
|
||||
var hidden = goog.dom.getElementsByClass(hiddenCssName, parentElt);
|
||||
|
||||
for (var i = 0; i < shown.length; i++) {
|
||||
goog.dom.classlist.addRemove(shown[i], shownCssName, hiddenCssName);
|
||||
}
|
||||
|
||||
// Then add editing styles.
|
||||
var editingCssName = goog.getCssName('editing');
|
||||
var startingEdit = !goog.dom.classlist.contains(parentElt, editingCssName);
|
||||
if (startingEdit) {
|
||||
goog.dom.classlist.remove(parentElt, editingCssName);
|
||||
} else {
|
||||
goog.dom.classlist.add(parentElt, editingCssName);
|
||||
}
|
||||
|
||||
// The show hiddens.
|
||||
for (var i = 0; i < hidden.length; i++) {
|
||||
goog.dom.classlist.addRemove(hidden[i], hiddenCssName, shownCssName);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Subclasses should override to enhance the default model.
|
||||
* @return {!Object.<string, ?>}
|
||||
*/
|
||||
registry.EditItem.prototype.newModel = function() {
|
||||
return {item: {}};
|
||||
};
|
||||
|
||||
|
||||
// N.B. setting these as abstract precludes their correct binding in
|
||||
// setupAppbar.
|
||||
/** Show add item panel. */
|
||||
registry.EditItem.prototype.add = function() {};
|
||||
|
||||
|
||||
/** Go back from item to collection view. */
|
||||
registry.EditItem.prototype.back = function() {};
|
||||
|
||||
|
||||
/** Sets up initial edit model state and then called edit(objArgs). */
|
||||
registry.EditItem.prototype.edit = function() {
|
||||
var objArgs = this.model;
|
||||
if (objArgs == null) {
|
||||
objArgs = this.newModel();
|
||||
}
|
||||
// XXX: This is vestigial. In the new msg format, this pollutes the
|
||||
// server-model state. Should be carried elsewhere.
|
||||
objArgs.readonly = false;
|
||||
this.renderItem(objArgs);
|
||||
this.setupEditor(objArgs);
|
||||
this.toggleEdit();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Save the current item. Calls either create if creating a new
|
||||
* object or update otherwise.
|
||||
*/
|
||||
registry.EditItem.prototype.save = function() {
|
||||
if (this.model == null) {
|
||||
this.sendCreate();
|
||||
} else {
|
||||
this.sendUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Resets to non-editing, readonly state, or visits home screen if the
|
||||
* page was in on a create panel.
|
||||
*/
|
||||
registry.EditItem.prototype.cancel = function() {
|
||||
this.toggleEdit();
|
||||
// XXX: The presence of a model is sufficent for non-collection pages, but an
|
||||
// empty id also means go to the collection in collection pages. Should
|
||||
// be simplified.
|
||||
if (this.model && this.id != '') {
|
||||
this.model.readonly = true;
|
||||
this.renderItem(this.model);
|
||||
} else {
|
||||
this.bindToDom('');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Called after this.renderItem(), to allow for further setup of
|
||||
* editing.
|
||||
* @param {!Object} objArgs
|
||||
*/
|
||||
registry.EditItem.prototype.setupEditor = function(objArgs) {};
|
||||
|
||||
|
||||
// XXX: These should really take @param {object} which is the form. Alas the
|
||||
// only override which doesn't work this way, ResourceComponent.sendCreate
|
||||
// breaks this opportunity. Hmmm...
|
||||
/** Subclasses should extract form values and send them to the server. */
|
||||
registry.EditItem.prototype.sendCreate = goog.abstractMethod;
|
||||
|
||||
|
||||
/** Subclasses should extract form values and send them to the server. */
|
||||
registry.EditItem.prototype.sendUpdate = goog.abstractMethod;
|
||||
|
||||
|
||||
/** Subclasses should extract form values and send them to the server. */
|
||||
registry.EditItem.prototype.sendDelete = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Sublcasses should override to populate update queryParams with form
|
||||
* fields as needed. {@code queryParams.nextId} MUST be set to the
|
||||
* new object's ID.
|
||||
* @param {!Object} queryParams
|
||||
*/
|
||||
registry.EditItem.prototype.prepareUpdate = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Subclasses should provide a funtion to parse either XML or JSON response
|
||||
* from server and return a result object as described below.
|
||||
* @param {!Object} rsp Decoded XML/JSON response from the server.
|
||||
* @return {!Object} a result object describing next steps. On
|
||||
* success, if next is defined, visit(ret.next) is called, otherwise
|
||||
* if err is set, the butterbar message is set to it.
|
||||
*/
|
||||
registry.EditItem.prototype.handleUpdateResponse = goog.abstractMethod;
|
135
java/google/registry/ui/js/forms.js
Normal file
|
@ -0,0 +1,135 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
goog.provide('registry.forms');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.dom.classlist');
|
||||
goog.require('goog.dom.forms');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.events.KeyCodes');
|
||||
goog.require('registry.util');
|
||||
|
||||
|
||||
/**
|
||||
* Sets the focus on a form field (if it exists).
|
||||
* @param {Element|string} field Form field (or ID) to focus.
|
||||
*/
|
||||
registry.forms.focus = function(field) {
|
||||
field = goog.dom.getElement(field);
|
||||
if (!goog.isNull(field) && goog.dom.isFocusable(field)) {
|
||||
goog.dom.forms.focusAndSelect(field);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Displays a form field error, or butters if no field is specified.
|
||||
* @param {string} message Human-readable explanation of why this field is evil.
|
||||
* @param {string=} opt_field Erroneous field name.
|
||||
*/
|
||||
registry.forms.displayError = function(message, opt_field) {
|
||||
if (!goog.isDef(opt_field)) {
|
||||
registry.util.butter(message);
|
||||
return;
|
||||
}
|
||||
var input = goog.dom.getElement(opt_field) ||
|
||||
goog.dom.getElement(opt_field + '[0]');
|
||||
// XXX: Transitioning to use of form.eltId instead of DOM id. If DOM id
|
||||
// lookup fails, then search forms for the named field.
|
||||
if (goog.isDefAndNotNull(opt_field) && goog.isNull(input)) {
|
||||
for (var fNdx in document.forms) {
|
||||
var form = document.forms[fNdx];
|
||||
if (form[opt_field]) {
|
||||
input = form[opt_field];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!goog.isNull(input)) {
|
||||
goog.dom.classlist.add(input, goog.getCssName('kd-formerror'));
|
||||
goog.dom.insertSiblingAfter(
|
||||
goog.dom.createDom(goog.dom.TagName.DIV,
|
||||
goog.getCssName('kd-errormessage'),
|
||||
message),
|
||||
input);
|
||||
registry.forms.focus(input);
|
||||
} else {
|
||||
registry.util.butter(opt_field + ': ' + message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** Removes error markup from whois settings form. */
|
||||
registry.forms.resetErrors = function() {
|
||||
registry.util.unbutter();
|
||||
goog.array.forEach(
|
||||
goog.dom.getElementsByClass(goog.getCssName('kd-formerror')),
|
||||
function(field) {
|
||||
goog.dom.classlist.remove(field, goog.getCssName('kd-formerror'));
|
||||
});
|
||||
goog.array.forEach(
|
||||
goog.dom.getElementsByClass(goog.getCssName('kd-errormessage')),
|
||||
goog.dom.removeNode);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds enter key listeners to all form fields.
|
||||
* @param {!Element} container Parent element containing INPUT fields.
|
||||
* @param {function()} callback Called when enter is pressed in a field.
|
||||
*/
|
||||
registry.forms.listenFieldsOnEnter = function(container, callback) {
|
||||
var handler = goog.partial(registry.forms.onFieldKeyUp_, callback);
|
||||
goog.array.forEach(
|
||||
goog.dom.getElementsByTagNameAndClass(
|
||||
goog.dom.TagName.INPUT, undefined, container),
|
||||
function(field) {
|
||||
goog.events.listen(field, goog.events.EventType.KEYUP, handler);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Event handler that saves form when enter is pressed in a form field.
|
||||
* @param {function()} callback Called when key pressed is ENTER.
|
||||
* @param {!goog.events.KeyEvent} e Key event to handle.
|
||||
* @return {boolean} Whether the event should be continued or cancelled.
|
||||
* @private
|
||||
*/
|
||||
registry.forms.onFieldKeyUp_ = function(callback, e) {
|
||||
if (e.keyCode == goog.events.KeyCodes.ENTER) {
|
||||
callback();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Toggles disabled state of a button or form element.
|
||||
* @param {!Element} element Form element to disable.
|
||||
* @param {boolean} enabled Enables element if true, or else disables it.
|
||||
*/
|
||||
registry.forms.setEnabled = function(element, enabled) {
|
||||
if (enabled) {
|
||||
goog.dom.classlist.remove(element, goog.getCssName('disabled'));
|
||||
} else {
|
||||
goog.dom.classlist.add(element, goog.getCssName('disabled'));
|
||||
element.blur();
|
||||
}
|
||||
};
|
135
java/google/registry/ui/js/menu_button.js
Normal file
|
@ -0,0 +1,135 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
goog.provide('registry.MenuButton');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.classlist');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventHandler');
|
||||
goog.require('goog.events.EventType');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Kennedy style menu button.
|
||||
* @param {!Element} button Menu button element.
|
||||
* @constructor
|
||||
* @extends {goog.events.EventHandler}
|
||||
* @final
|
||||
*/
|
||||
registry.MenuButton = function(button) {
|
||||
registry.MenuButton.base(this, 'constructor');
|
||||
|
||||
/**
|
||||
* Outer menu button element.
|
||||
* @private {!Element}
|
||||
* @const
|
||||
*/
|
||||
this.button_ = button;
|
||||
this.listen(button, goog.events.EventType.CLICK, this.onButtonClick_);
|
||||
|
||||
/**
|
||||
* Label that displays currently selected item.
|
||||
* @private {!Element}
|
||||
* @const
|
||||
*/
|
||||
this.label_ =
|
||||
goog.dom.getRequiredElementByClass(goog.getCssName('label'), button);
|
||||
|
||||
/**
|
||||
* List of selectable items.
|
||||
* @private {!Element}
|
||||
* @const
|
||||
*/
|
||||
this.menu_ =
|
||||
goog.dom.getRequiredElementByClass(goog.getCssName('kd-menulist'),
|
||||
button);
|
||||
|
||||
goog.array.forEach(
|
||||
goog.dom.getElementsByClass(goog.getCssName('kd-menulistitem'), button),
|
||||
function(item) {
|
||||
this.listen(item, goog.events.EventType.CLICK, this.onItemClick_);
|
||||
},
|
||||
this);
|
||||
};
|
||||
goog.inherits(registry.MenuButton, goog.events.EventHandler);
|
||||
|
||||
|
||||
/**
|
||||
* Returns selected value in menu.
|
||||
* @return {string}
|
||||
*/
|
||||
registry.MenuButton.prototype.getValue = function() {
|
||||
return goog.dom.getTextContent(
|
||||
goog.dom.getRequiredElementByClass(
|
||||
goog.getCssName('selected'),
|
||||
this.button_));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Main menu button handler.
|
||||
* @param {!goog.events.BrowserEvent} e
|
||||
* @private
|
||||
*/
|
||||
registry.MenuButton.prototype.onButtonClick_ = function(e) {
|
||||
if (goog.dom.classlist.contains(this.button_, goog.getCssName('selected'))) {
|
||||
return;
|
||||
}
|
||||
e.stopPropagation();
|
||||
goog.dom.classlist.add(this.button_, goog.getCssName('selected'));
|
||||
goog.dom.classlist.add(this.menu_, goog.getCssName('shown'));
|
||||
this.listenOnce(goog.dom.getDocument().body, goog.events.EventType.CLICK,
|
||||
this.hideMenu_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Menu item selection handler.
|
||||
* @param {!goog.events.BrowserEvent} e
|
||||
* @private
|
||||
*/
|
||||
registry.MenuButton.prototype.onItemClick_ = function(e) {
|
||||
e.stopPropagation();
|
||||
if (goog.dom.classlist.contains(this.button_, goog.getCssName('disabled'))) {
|
||||
return;
|
||||
}
|
||||
goog.array.forEach(
|
||||
goog.dom.getElementsByClass(goog.getCssName('kd-menulistitem'),
|
||||
this.button_),
|
||||
function(item) {
|
||||
goog.dom.classlist.remove(item, goog.getCssName('selected'));
|
||||
},
|
||||
this);
|
||||
goog.asserts.assert(e.target instanceof Element);
|
||||
goog.dom.classlist.add(e.target, goog.getCssName('selected'));
|
||||
var text = goog.dom.getTextContent(e.target);
|
||||
goog.dom.setTextContent(this.label_, text);
|
||||
goog.events.fireListeners(this.button_, goog.events.EventType.CHANGE,
|
||||
false, {newValue: text});
|
||||
this.hideMenu_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hide the menu.
|
||||
* @private
|
||||
*/
|
||||
registry.MenuButton.prototype.hideMenu_ = function() {
|
||||
goog.dom.classlist.remove(this.menu_, goog.getCssName('shown'));
|
||||
goog.dom.classlist.remove(this.button_, goog.getCssName('selected'));
|
||||
};
|
31
java/google/registry/ui/js/registrar/BUILD
Normal file
|
@ -0,0 +1,31 @@
|
|||
package(default_visibility = ["//java/com/google/domain/registry:registry_project"])
|
||||
|
||||
load("//third_party/closure/compiler:closure_js_library.bzl", "closure_js_library")
|
||||
|
||||
|
||||
filegroup(
|
||||
name = "js_files",
|
||||
srcs = glob(["*.js"]),
|
||||
)
|
||||
|
||||
closure_js_library(
|
||||
name = "registrar",
|
||||
srcs = [":js_files"],
|
||||
deps = [
|
||||
"//java/com/google/domain/registry/ui/js",
|
||||
"//java/com/google/domain/registry/ui/soy:Forms",
|
||||
"//java/com/google/domain/registry/ui/soy/registrar:Console",
|
||||
"//java/com/google/domain/registry/ui/soy/registrar:Contact",
|
||||
"//java/com/google/domain/registry/ui/soy/registrar:ContactEpp",
|
||||
"//java/com/google/domain/registry/ui/soy/registrar:ContactSettings",
|
||||
"//java/com/google/domain/registry/ui/soy/registrar:Domain",
|
||||
"//java/com/google/domain/registry/ui/soy/registrar:DomainEpp",
|
||||
"//java/com/google/domain/registry/ui/soy/registrar:Epp",
|
||||
"//java/com/google/domain/registry/ui/soy/registrar:Host",
|
||||
"//java/com/google/domain/registry/ui/soy/registrar:HostEpp",
|
||||
"//java/com/google/domain/registry/ui/soy/registrar:Payment",
|
||||
"//java/com/google/domain/registry/ui/soy/registrar:SecuritySettings",
|
||||
"//java/com/google/domain/registry/ui/soy/registrar:WhoisSettings",
|
||||
"//javascript/closure",
|
||||
],
|
||||
)
|
265
java/google/registry/ui/js/registrar/brainframe.js
Normal file
|
@ -0,0 +1,265 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
goog.provide('registry.registrar.BrainFrame');
|
||||
goog.provide('registry.registrar.BrainFrame.main');
|
||||
|
||||
goog.require('goog.Timer');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.events.EventHandler');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.json');
|
||||
goog.require('goog.object');
|
||||
goog.require('goog.style');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sandboxed iframe for Braintree JS SDK v2 iframe.
|
||||
*
|
||||
* <p>This class adds an additional layer of security between the Registrar
|
||||
* Console and JavaScript loaded from Braintree's web server.
|
||||
*
|
||||
* <p>The main function for this class is compiled into a separate binary,
|
||||
* which is loaded within an iframe that's hosted on a different domain than
|
||||
* the production environment. This ensures that cross origin browser security
|
||||
* policies take effect.
|
||||
*
|
||||
* @param {string} origin FQDN of production environment.
|
||||
* @param {string} containerId ID of Braintree container element.
|
||||
* @constructor
|
||||
* @extends {goog.events.EventHandler}
|
||||
* @final
|
||||
*/
|
||||
registry.registrar.BrainFrame = function(origin, containerId) {
|
||||
registry.registrar.BrainFrame.base(this, 'constructor');
|
||||
|
||||
/**
|
||||
* Hostname of production registry, e.g. domain-registry.appspot.com.
|
||||
* @private {string}
|
||||
* @const
|
||||
*/
|
||||
this.origin_ = origin;
|
||||
|
||||
/**
|
||||
* Div that wraps Braintree iframe.
|
||||
* @private {!Element}
|
||||
* @const
|
||||
*/
|
||||
this.container_ = goog.dom.getRequiredElement(containerId);
|
||||
|
||||
/**
|
||||
* Last known height of {@code container_}.
|
||||
* @private {number}
|
||||
*/
|
||||
this.containerHeight_ = 0;
|
||||
|
||||
/**
|
||||
* Timer polling for changes in Braintree iframe height.
|
||||
* @private {!goog.Timer}
|
||||
* @const
|
||||
*/
|
||||
this.resizeTimer_ = new goog.Timer(1000 / 30);
|
||||
this.registerDisposable(this.resizeTimer_);
|
||||
this.listen(this.resizeTimer_, goog.Timer.TICK, this.onResizeTimer_);
|
||||
|
||||
/**
|
||||
* Form that wraps {@code container_}.
|
||||
* @private {?Element}
|
||||
* @const
|
||||
*/
|
||||
this.form_ = goog.dom.getAncestorByTagNameAndClass(this.container_,
|
||||
goog.dom.TagName.FORM);
|
||||
goog.asserts.assert(this.form_ != null);
|
||||
|
||||
/**
|
||||
* State indicating if we're submitting at behest of parent.
|
||||
* @private {boolean}
|
||||
*/
|
||||
this.isSubmitting_ = false;
|
||||
|
||||
this.listen(goog.global.window,
|
||||
goog.events.EventType.MESSAGE,
|
||||
this.onMessage_);
|
||||
};
|
||||
goog.inherits(registry.registrar.BrainFrame, goog.events.EventHandler);
|
||||
|
||||
|
||||
/**
|
||||
* Runs Braintree sandbox environment.
|
||||
*/
|
||||
registry.registrar.BrainFrame.prototype.run = function() {
|
||||
this.send_(
|
||||
'type', registry.registrar.BrainFrame.MessageType.TOKEN_REQUEST);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles message from parent iframe which sends Braintree token.
|
||||
* @param {!goog.events.BrowserEvent} e
|
||||
* @private
|
||||
*/
|
||||
registry.registrar.BrainFrame.prototype.onMessage_ = function(e) {
|
||||
var msg = /** @type {!MessageEvent.<string>} */ (e.getBrowserEvent());
|
||||
if (msg.source != goog.global.window.parent) {
|
||||
return;
|
||||
}
|
||||
if (this.origin_ != '*' && this.origin_ != msg.origin) {
|
||||
throw new Error(
|
||||
'Message origin is "' + msg.origin + '" but wanted: ' + this.origin_);
|
||||
}
|
||||
var data = goog.json.parse(msg.data);
|
||||
switch (goog.object.get(data, 'type')) {
|
||||
case registry.registrar.BrainFrame.MessageType.TOKEN_RESPONSE:
|
||||
goog.global.braintree.setup(goog.object.get(data, 'token'), 'dropin', {
|
||||
container: this.container_,
|
||||
onPaymentMethodReceived: goog.bind(this.onPaymentMethod_, this),
|
||||
onReady: goog.bind(this.onReady_, this),
|
||||
onError: goog.bind(this.onError_, this)
|
||||
});
|
||||
this.resizeTimer_.start();
|
||||
break;
|
||||
case registry.registrar.BrainFrame.MessageType.SUBMIT_REQUEST:
|
||||
this.isSubmitting_ = true;
|
||||
// Trigger Braintree JS SDK submit event listener. It does not appear to
|
||||
// be possible to do this using the Closure Library. This is IE 9+ only.
|
||||
this.form_.dispatchEvent(new Event(goog.events.EventType.SUBMIT));
|
||||
break;
|
||||
default:
|
||||
throw Error('Unexpected message: ' + msg.data);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Polls for resizes of Braintree iframe and propagates them to the parent
|
||||
* frame which will then use it to resize this iframe.
|
||||
* @private
|
||||
*/
|
||||
registry.registrar.BrainFrame.prototype.onResizeTimer_ = function() {
|
||||
var height = goog.style.getSize(this.container_).height;
|
||||
if (height != this.containerHeight_) {
|
||||
this.send_(
|
||||
'type', registry.registrar.BrainFrame.MessageType.RESIZE_REQUEST,
|
||||
'height', height);
|
||||
this.containerHeight_ = height;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback Braintree iframe has fully loaded.
|
||||
* @private
|
||||
*/
|
||||
registry.registrar.BrainFrame.prototype.onReady_ = function() {
|
||||
this.send_('type', registry.registrar.BrainFrame.MessageType.READY);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback Braintree says an error happened.
|
||||
* @param {!braintreepayments.Error} error
|
||||
* @private
|
||||
*/
|
||||
registry.registrar.BrainFrame.prototype.onError_ = function(error) {
|
||||
this.isSubmitting_ = false;
|
||||
this.send_('type', registry.registrar.BrainFrame.MessageType.SUBMIT_ERROR,
|
||||
'message', error.message);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback when user successfully gave Braintree payment details.
|
||||
* @param {!braintreepayments.PaymentMethod} pm
|
||||
* @private
|
||||
*/
|
||||
registry.registrar.BrainFrame.prototype.onPaymentMethod_ = function(pm) {
|
||||
// TODO(b/26829319): The Braintree JS SDK does not seem to recognize the
|
||||
// enter key while embedded inside our sandbox iframe. So
|
||||
// at this time, this callback will only be invoked after
|
||||
// we've submitted the form manually at the behest of
|
||||
// payment.js, which means isSubmitting_ will be true.
|
||||
this.send_(
|
||||
'type', registry.registrar.BrainFrame.MessageType.PAYMENT_METHOD,
|
||||
'submit', this.isSubmitting_,
|
||||
'method', pm);
|
||||
this.isSubmitting_ = false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends message to parent iframe.
|
||||
* @param {...*} var_args Passed along to {@code goog.object.create}.
|
||||
* @private
|
||||
*/
|
||||
registry.registrar.BrainFrame.prototype.send_ = function(var_args) {
|
||||
goog.asserts.assert(arguments[0] == 'type');
|
||||
registry.registrar.BrainFrame.postMessage_(
|
||||
goog.json.serialize(goog.object.create.apply(null, arguments)),
|
||||
this.origin_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Delegates to {@code window.parent.postMessage}. This method exists because
|
||||
* IE will not allow us to mock methods on the window object.
|
||||
* @param {string} message
|
||||
* @param {string} origin
|
||||
* @private
|
||||
*/
|
||||
registry.registrar.BrainFrame.postMessage_ = function(message, origin) {
|
||||
goog.global.window.parent.postMessage(message, origin);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Message types passed between brainframe and payment page.
|
||||
* @enum {string}
|
||||
*/
|
||||
registry.registrar.BrainFrame.MessageType = {
|
||||
|
||||
/** Brainframe asks payment page for Braintree token. */
|
||||
TOKEN_REQUEST: 'token_request',
|
||||
|
||||
/** Payment page sends brainframe Braintree token. */
|
||||
TOKEN_RESPONSE: 'token_response',
|
||||
|
||||
/** Brainframe asks payment page to be resized. */
|
||||
RESIZE_REQUEST: 'resize_request',
|
||||
|
||||
/** Brainframe tells payment page it finished loading. */
|
||||
READY: 'ready',
|
||||
|
||||
/** Payment page asks brainframe to submit Braintree payment method form. */
|
||||
SUBMIT_REQUEST: 'submit_request',
|
||||
|
||||
/** Brainframe tells payment page it failed to submit. */
|
||||
SUBMIT_ERROR: 'submit_error',
|
||||
|
||||
/** Brainframe gives payment method info and nonce to payment page. */
|
||||
PAYMENT_METHOD: 'payment_method'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Entrypoint for {@link registry.registrar.BrainFrame}.
|
||||
* @param {string} origin
|
||||
* @param {string} containerId
|
||||
* @export
|
||||
*/
|
||||
registry.registrar.BrainFrame.main = function(origin, containerId) {
|
||||
new registry.registrar.BrainFrame(origin, containerId).run();
|
||||
};
|
167
java/google/registry/ui/js/registrar/console.js
Normal file
|
@ -0,0 +1,167 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
goog.provide('registry.registrar.Console');
|
||||
|
||||
goog.require('goog.dispose');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.classlist');
|
||||
goog.require('goog.net.XhrIo');
|
||||
goog.require('registry.Console');
|
||||
goog.require('registry.registrar.Contact');
|
||||
goog.require('registry.registrar.ContactSettings');
|
||||
goog.require('registry.registrar.ContactUs');
|
||||
goog.require('registry.registrar.Dashboard');
|
||||
goog.require('registry.registrar.Domain');
|
||||
goog.require('registry.registrar.EppSession');
|
||||
goog.require('registry.registrar.Host');
|
||||
goog.require('registry.registrar.Payment');
|
||||
goog.require('registry.registrar.Resources');
|
||||
goog.require('registry.registrar.SecuritySettings');
|
||||
goog.require('registry.registrar.WhoisSettings');
|
||||
goog.require('registry.util');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The Registrar Console.
|
||||
* @param {string} xsrfToken Populated by server-side soy template.
|
||||
* @param {string} clientId The logged in GAE user.
|
||||
* @constructor
|
||||
* @extends {registry.Console}
|
||||
* @final
|
||||
*/
|
||||
registry.registrar.Console = function(xsrfToken, clientId) {
|
||||
registry.registrar.Console.base(
|
||||
this, 'constructor',
|
||||
new registry.registrar.EppSession(this, xsrfToken, clientId));
|
||||
|
||||
/**
|
||||
* Component that's currently embedded in the page.
|
||||
* @type {?registry.Component}
|
||||
* @private
|
||||
*/
|
||||
this.component_ = null;
|
||||
|
||||
// XXX: This was in parent ctor but was triggering event dispatching before
|
||||
// ready here.
|
||||
this.history.setEnabled(true);
|
||||
|
||||
/**
|
||||
* @type {!string}
|
||||
* @private
|
||||
*/
|
||||
this.xsrfToken_ = xsrfToken;
|
||||
|
||||
/**
|
||||
* Last active nav element.
|
||||
* @type {Element}
|
||||
*/
|
||||
this.lastActiveNavElt;
|
||||
|
||||
/**
|
||||
* @type {!Object.<string, function(new:registry.Component,
|
||||
* !registry.registrar.Console,
|
||||
* string)>}
|
||||
*/
|
||||
this.pageMap = {};
|
||||
this.pageMap['security-settings'] = registry.registrar.SecuritySettings;
|
||||
this.pageMap['contact-settings'] = registry.registrar.ContactSettings;
|
||||
this.pageMap['whois-settings'] = registry.registrar.WhoisSettings;
|
||||
this.pageMap['contact-us'] = registry.registrar.ContactUs;
|
||||
this.pageMap['resources'] = registry.registrar.Resources;
|
||||
this.pageMap['contact'] = registry.registrar.Contact;
|
||||
this.pageMap['payment'] = registry.registrar.Payment;
|
||||
this.pageMap['domain'] = registry.registrar.Domain;
|
||||
this.pageMap['host'] = registry.registrar.Host;
|
||||
this.pageMap[''] = registry.registrar.Dashboard;
|
||||
};
|
||||
goog.inherits(registry.registrar.Console, registry.Console);
|
||||
|
||||
|
||||
/**
|
||||
* Changes the content area depending on hash path.
|
||||
*
|
||||
* <p>Hash path is expected to be of the form:
|
||||
*
|
||||
* <pre>
|
||||
* #type/id
|
||||
* </pre>
|
||||
*
|
||||
* <p>The {@code id} part may be appended by {@code ()} to specify the target
|
||||
* should be a resource create page.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
registry.registrar.Console.prototype.handleHashChange = function() {
|
||||
var hashToken = this.history.getToken();
|
||||
// On page reloads, opening a new tab, etc. it's possible that the
|
||||
// session cookie for a logged-in session exists, but the
|
||||
// this.session is not yet aware, so come back here after syncing.
|
||||
//
|
||||
// XXX: Method should be refactored to avoid this 2-stage behavior.
|
||||
if (!this.session.isEppLoggedIn()) {
|
||||
this.session.login(goog.bind(this.handleHashChange, this));
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, a resource operation.
|
||||
var parts = hashToken.split('/');
|
||||
var type = '';
|
||||
var id = '';
|
||||
if (parts.length >= 1) {
|
||||
type = parts[0];
|
||||
}
|
||||
if (parts.length == 2) {
|
||||
id = parts[1];
|
||||
}
|
||||
|
||||
goog.net.XhrIo.cleanup();
|
||||
|
||||
var componentCtor = this.pageMap[type];
|
||||
if (componentCtor == undefined) {
|
||||
componentCtor = this.pageMap[''];
|
||||
}
|
||||
var oldComponent = this.component_;
|
||||
this.component_ = new componentCtor(this, this.xsrfToken_);
|
||||
this.registerDisposable(this.component_);
|
||||
this.component_.basePath = type;
|
||||
this.component_.bindToDom(id);
|
||||
|
||||
this.changeNavStyle();
|
||||
|
||||
goog.dispose(oldComponent);
|
||||
};
|
||||
|
||||
|
||||
/** Change nav style. */
|
||||
registry.registrar.Console.prototype.changeNavStyle = function() {
|
||||
var hashToken = this.history.getToken();
|
||||
// Path except id
|
||||
var slashNdx = hashToken.lastIndexOf('/');
|
||||
slashNdx = slashNdx == -1 ? hashToken.length : slashNdx;
|
||||
var regNavlist = goog.dom.getRequiredElement('reg-navlist');
|
||||
var path = hashToken.substring(0, slashNdx);
|
||||
var active = regNavlist.querySelector('a[href="/registrar#' + path + '"]');
|
||||
if (goog.isNull(active)) {
|
||||
registry.util.log('Unknown path or path form in changeNavStyle.');
|
||||
return;
|
||||
}
|
||||
if (this.lastActiveNavElt) {
|
||||
goog.dom.classlist.remove(
|
||||
this.lastActiveNavElt, goog.getCssName('domain-active-nav'));
|
||||
}
|
||||
goog.dom.classlist.add(active, goog.getCssName('domain-active-nav'));
|
||||
this.lastActiveNavElt = active;
|
||||
};
|
125
java/google/registry/ui/js/registrar/contact.js
Normal file
|
@ -0,0 +1,125 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
goog.provide('registry.registrar.Contact');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('registry.registrar.XmlResourceComponent');
|
||||
goog.require('registry.soy.registrar.contact');
|
||||
goog.require('registry.soy.registrar.contactepp');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The {@code Contact} class respresents a registry contact object and
|
||||
* binds UI CRUD operations to it.
|
||||
* @param {!registry.registrar.Console} console the
|
||||
* console singleton.
|
||||
* @constructor
|
||||
* @extends {registry.registrar.XmlResourceComponent}
|
||||
* @final
|
||||
*/
|
||||
registry.registrar.Contact = function(console) {
|
||||
registry.registrar.Contact.base(
|
||||
this, 'constructor',
|
||||
registry.soy.registrar.contact.item,
|
||||
registry.soy.registrar.contactepp,
|
||||
console);
|
||||
};
|
||||
goog.inherits(registry.registrar.Contact,
|
||||
registry.registrar.XmlResourceComponent);
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.Contact.prototype.processItem = function() {
|
||||
this.model.item = this.model['epp']['response']['resData']['contact:infData'];
|
||||
if (!goog.isArray(this.model.item['contact:postalInfo'])) {
|
||||
this.model.item['contact:postalInfo'] =
|
||||
[this.model.item['contact:postalInfo']];
|
||||
}
|
||||
|
||||
// XXX: Is this code necessary?
|
||||
var fixPlus = function(val) {
|
||||
var str = (val || '') + '';
|
||||
if (str == '' || str.match(/\+.*/)) {
|
||||
return str;
|
||||
} else {
|
||||
return '+' + str;
|
||||
}
|
||||
};
|
||||
|
||||
// Both of these are optional.
|
||||
if (this.model.item['contact:voice']) {
|
||||
this.model.item['contact:voice']['keyValue'] =
|
||||
fixPlus(this.model.item['contact:voice']['keyValue']);
|
||||
}
|
||||
if (this.model.item['contact:voice']) {
|
||||
this.model.item['contact:fax']['keyValue'] =
|
||||
fixPlus(this.model.item['contact:fax']['keyValue']);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.Contact.prototype.setupEditor = function(objArgs) {
|
||||
// For now always keep the first contact and make it i18n. Toggle button
|
||||
// disables to enforce state.
|
||||
//
|
||||
// XXX: Should be simplified to make more modular.
|
||||
var postalElt = goog.dom.getRequiredElement('contact-postalInfo');
|
||||
var addPostalInfoBtn = goog.dom.getRequiredElement(
|
||||
'domain-contact-postalInfo-add-button');
|
||||
this.typeCounts['contact-postalInfo'] = postalElt.childNodes.length;
|
||||
// 4 child nodes means both addresses are present:
|
||||
// 2 data tables, the footer id elt and a hidden input.
|
||||
var setupRemoveBtns = this.typeCounts['contact-postalInfo'] == 4;
|
||||
if (setupRemoveBtns) {
|
||||
this.appendRemoveBtn(/** @type {!Element} */ (postalElt.childNodes[0]));
|
||||
this.appendRemoveBtn(/** @type {!Element} */ (postalElt.childNodes[1]));
|
||||
} else {
|
||||
addPostalInfoBtn.removeAttribute('disabled');
|
||||
}
|
||||
|
||||
this.addRemBtnHandlers(
|
||||
'contact-postalInfo',
|
||||
function() {
|
||||
return 'contact:postalInfo[1].contact:';
|
||||
},
|
||||
function() {
|
||||
addPostalInfoBtn.setAttribute('disabled', true);
|
||||
return null;
|
||||
},
|
||||
registry.soy.registrar.contact.postalInfo,
|
||||
{
|
||||
item: {},
|
||||
localized: true,
|
||||
itemPrefix: 'contact:',
|
||||
namePrefix: 'contact:postalInfo[1].contact:'
|
||||
},
|
||||
setupRemoveBtns);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.Contact.prototype.prepareCreate = function(params) {
|
||||
params.nextId = params.item['contact:id'];
|
||||
return registry.soy.registrar.contactepp.create(params).toString();
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.Contact.prototype.prepareUpdate = function(params) {
|
||||
params.nextId = params.item['contact:id'];
|
||||
return registry.soy.registrar.contactepp.update(params).toString();
|
||||
};
|
240
java/google/registry/ui/js/registrar/contact_settings.js
Normal file
|
@ -0,0 +1,240 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
goog.provide('registry.registrar.ContactSettings');
|
||||
|
||||
goog.require('goog.Uri');
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.json');
|
||||
goog.require('goog.soy');
|
||||
goog.require('registry.Resource');
|
||||
goog.require('registry.ResourceComponent');
|
||||
goog.require('registry.soy.registrar.contacts');
|
||||
goog.require('registry.util');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Contact Settings page. Registrar Contacts are not really a proper
|
||||
* REST resource as they're still passed from the server as a member
|
||||
* field of Registrar, so this class behaves like an item page,
|
||||
* updating only that field of the Registrar object and not
|
||||
* implementing the create action from edit_item.
|
||||
* @param {!registry.registrar.Console} console
|
||||
* @param {string} xsrfToken Security token to pass back to the server.
|
||||
* @constructor
|
||||
* @extends {registry.ResourceComponent}
|
||||
* @final
|
||||
*/
|
||||
registry.registrar.ContactSettings = function(console, xsrfToken) {
|
||||
registry.registrar.ContactSettings.base(
|
||||
this, 'constructor',
|
||||
console,
|
||||
new registry.Resource(new goog.Uri('/registrar-settings'), xsrfToken),
|
||||
registry.soy.registrar.contacts.contact,
|
||||
null);
|
||||
};
|
||||
goog.inherits(registry.registrar.ContactSettings, registry.ResourceComponent);
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.ContactSettings.prototype.setupAppbar = function() {
|
||||
registry.registrar.ContactSettings.base(this, 'setupAppbar');
|
||||
// Setup delete only on existing items, not on creates.
|
||||
if (goog.isDefAndNotNull(this.model)) {
|
||||
var deleteBtn = goog.dom.createDom('button', {
|
||||
type: 'button',
|
||||
id: 'reg-app-btn-delete',
|
||||
className: goog.getCssName('kd-button')
|
||||
},
|
||||
'Delete');
|
||||
goog.events.listen(deleteBtn, goog.events.EventType.CLICK,
|
||||
goog.bind(this.sendDelete, this));
|
||||
goog.dom.insertSiblingBefore(deleteBtn,
|
||||
goog.dom.getRequiredElement('reg-app-btn-cancel'));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.ContactSettings.prototype.renderItem = function(rspObj) {
|
||||
var contentElt = goog.dom.getRequiredElement('reg-content');
|
||||
/** @type {!registry.json.RegistrarContact} */
|
||||
var contacts = rspObj.contacts;
|
||||
if (this.id) {
|
||||
var targetContactNdx;
|
||||
var targetContact;
|
||||
for (var c in contacts) {
|
||||
var ct = contacts[c];
|
||||
if (ct.emailAddress == this.id) {
|
||||
targetContactNdx = c;
|
||||
targetContact = ct;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!targetContact) {
|
||||
registry.util.butter('No contact with the given email.');
|
||||
return;
|
||||
}
|
||||
var typesList = targetContact.types.toLowerCase().split(',');
|
||||
var actualTypesLookup = {};
|
||||
for (var t in typesList) {
|
||||
actualTypesLookup[typesList[t]] = true;
|
||||
}
|
||||
goog.soy.renderElement(
|
||||
contentElt,
|
||||
registry.soy.registrar.contacts.contact,
|
||||
{
|
||||
item: targetContact,
|
||||
namePrefix: 'contacts[' + targetContactNdx + '].',
|
||||
actualTypesLookup: actualTypesLookup,
|
||||
readonly: (rspObj.readonly || false)
|
||||
});
|
||||
this.setupAppbar();
|
||||
} else {
|
||||
var contactsByType = {};
|
||||
for (var c in contacts) {
|
||||
var contact = contacts[c];
|
||||
var types = contact.types;
|
||||
if (!types) {
|
||||
continue;
|
||||
}
|
||||
types = types.split(',');
|
||||
for (var t in types) {
|
||||
var type = types[t].toLowerCase();
|
||||
var contactsList = contactsByType[type];
|
||||
if (!contactsList) {
|
||||
contactsByType[type] = contactsList = [];
|
||||
}
|
||||
contactsList.push(contact);
|
||||
}
|
||||
}
|
||||
goog.soy.renderElement(
|
||||
contentElt,
|
||||
registry.soy.registrar.contacts.set,
|
||||
{contactsByType: contactsByType });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.ContactSettings.prototype.add = function() {
|
||||
var newContactNdx = this.model.contacts.length;
|
||||
goog.soy.renderElement(goog.dom.getRequiredElement('reg-content'),
|
||||
registry.soy.registrar.contacts.contact,
|
||||
{
|
||||
item: {},
|
||||
namePrefix: 'contacts[' + newContactNdx + '].',
|
||||
actualTypesLookup: {},
|
||||
readonly: false
|
||||
});
|
||||
this.toggleEdit();
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.ContactSettings.prototype.sendDelete = function() {
|
||||
var ndxToDel = null;
|
||||
for (var i = 0; i < this.model.contacts.length; i++) {
|
||||
var contact = this.model.contacts[i];
|
||||
if (contact.emailAddress == this.id) {
|
||||
ndxToDel = i;
|
||||
}
|
||||
}
|
||||
if (goog.isNull(ndxToDel)) {
|
||||
throw new Error('Email to delete does not match model.');
|
||||
}
|
||||
var modelCopy = /** @type {!Object}
|
||||
*/ (goog.json.parse(goog.json.serialize(this.model)));
|
||||
goog.array.removeAt(modelCopy.contacts, ndxToDel);
|
||||
this.resource.update(modelCopy, goog.bind(this.handleDeleteResponse, this));
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.ContactSettings.prototype.prepareUpdate =
|
||||
function(modelCopy) {
|
||||
var form = registry.util.parseForm('item');
|
||||
var contact;
|
||||
// Handle update/create.
|
||||
if (this.id) {
|
||||
// Update contact, so overwrite it in the model before sending
|
||||
// back to server.
|
||||
var once = false;
|
||||
for (var c in form.contacts) {
|
||||
if (once) {
|
||||
throw new Error('More than one contact parsed from form: ' + c);
|
||||
}
|
||||
contact = form.contacts[c];
|
||||
modelCopy.contacts[c] = contact;
|
||||
once = true;
|
||||
}
|
||||
} else {
|
||||
// Add contact.
|
||||
contact = form.contacts.pop();
|
||||
modelCopy.contacts.push(contact);
|
||||
}
|
||||
contact.visibleInWhoisAsAdmin = contact.visibleInWhoisAsAdmin == 'true';
|
||||
contact.visibleInWhoisAsTech = contact.visibleInWhoisAsTech == 'true';
|
||||
contact.types = '';
|
||||
for (var tNdx in contact.type) {
|
||||
if (contact.type[tNdx]) {
|
||||
if (contact.types.length > 0) {
|
||||
contact.types += ',';
|
||||
}
|
||||
contact.types += ('' + tNdx).toUpperCase();
|
||||
}
|
||||
}
|
||||
delete contact['type'];
|
||||
this.nextId = contact.emailAddress;
|
||||
};
|
||||
|
||||
|
||||
// XXX: Should be hoisted up.
|
||||
/**
|
||||
* Handler for contact save that navigates to that item on success.
|
||||
* Does nothing on failure as UI will be left with error messages for
|
||||
* the user to resolve.
|
||||
* @param {!Object} rsp Decoded XML/JSON response from the server.
|
||||
* @override
|
||||
*/
|
||||
registry.registrar.ContactSettings.prototype.handleCreateResponse =
|
||||
function(rsp) {
|
||||
this.handleUpdateResponse(rsp);
|
||||
if (rsp.status == 'SUCCESS') {
|
||||
this.console.view('contact-settings/' + this.nextId);
|
||||
}
|
||||
return rsp;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for contact delete that navigates back to the collection on success.
|
||||
* Does nothing on failure as UI will be left with error messages for
|
||||
* the user to resolve.
|
||||
* @param {!Object} rsp Decoded XML/JSON response from the server.
|
||||
* @override
|
||||
*/
|
||||
registry.registrar.ContactSettings.prototype.handleDeleteResponse =
|
||||
function(rsp) {
|
||||
this.handleUpdateResponse(rsp);
|
||||
if (rsp.status == 'SUCCESS') {
|
||||
this.id = null;
|
||||
this.console.view('contact-settings');
|
||||
}
|
||||
return rsp;
|
||||
};
|
49
java/google/registry/ui/js/registrar/contact_us.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
goog.provide('registry.registrar.ContactUs');
|
||||
|
||||
goog.require('goog.Uri');
|
||||
goog.require('goog.dom');
|
||||
goog.require('registry.Resource');
|
||||
goog.require('registry.ResourceComponent');
|
||||
goog.require('registry.soy.registrar.console');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Contact Us page.
|
||||
* @param {!registry.registrar.Console} console
|
||||
* @param {string} xsrfToken Security token to pass back to the server.
|
||||
* @constructor
|
||||
* @extends {registry.ResourceComponent}
|
||||
* @final
|
||||
*/
|
||||
registry.registrar.ContactUs = function(console, xsrfToken) {
|
||||
registry.registrar.ContactUs.base(
|
||||
this,
|
||||
'constructor',
|
||||
console,
|
||||
new registry.Resource(new goog.Uri('/registrar-settings'), xsrfToken),
|
||||
registry.soy.registrar.console.contactUs,
|
||||
null);
|
||||
};
|
||||
goog.inherits(registry.registrar.ContactUs, registry.ResourceComponent);
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.ContactUs.prototype.bindToDom = function(id) {
|
||||
registry.registrar.ContactUs.base(this, 'bindToDom', '');
|
||||
goog.dom.removeChildren(goog.dom.getRequiredElement('reg-app-buttons'));
|
||||
};
|
94
java/google/registry/ui/js/registrar/dashboard.js
Normal file
|
@ -0,0 +1,94 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
goog.provide('registry.registrar.Dashboard');
|
||||
|
||||
goog.require('goog.Timer');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.soy');
|
||||
goog.require('goog.style');
|
||||
goog.require('registry.Component');
|
||||
goog.require('registry.soy.registrar.console');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Dashboard for Registrar Console.
|
||||
* @param {!registry.registrar.Console} console
|
||||
* @constructor
|
||||
* @extends {registry.Component}
|
||||
* @final
|
||||
*/
|
||||
registry.registrar.Dashboard = function(console) {
|
||||
registry.registrar.Dashboard.base(this, 'constructor', console);
|
||||
|
||||
/** @private {number} */
|
||||
this.x_ = 0;
|
||||
|
||||
/** @private {Element} */
|
||||
this.gear_ = null;
|
||||
|
||||
/** @private {goog.Timer} */
|
||||
this.timer_ = null;
|
||||
};
|
||||
goog.inherits(registry.registrar.Dashboard, registry.Component);
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.Dashboard.prototype.bindToDom = function(id) {
|
||||
registry.registrar.Dashboard.base(this, 'bindToDom', '');
|
||||
goog.dom.removeChildren(goog.dom.getRequiredElement('reg-appbar'));
|
||||
goog.soy.renderElement(goog.dom.getElement('reg-content'),
|
||||
registry.soy.registrar.console.dashboard);
|
||||
goog.events.listen(goog.dom.getElement('rotate'),
|
||||
goog.events.EventType.CLICK,
|
||||
goog.bind(this.rotate_, this));
|
||||
this.gear_ = goog.dom.getRequiredElement('gear');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Do EPP logout.
|
||||
*/
|
||||
registry.registrar.Dashboard.prototype.doEppLogout = function() {
|
||||
this.console.session.logout();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Let's do the twist.
|
||||
* @private
|
||||
*/
|
||||
registry.registrar.Dashboard.prototype.rotate_ = function() {
|
||||
this.timer_ = new goog.Timer(10);
|
||||
this.registerDisposable(this.timer_);
|
||||
goog.events.listen(this.timer_, goog.Timer.TICK,
|
||||
goog.bind(this.rotateCall_, this));
|
||||
this.timer_.start();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* No really this time!
|
||||
* @private
|
||||
*/
|
||||
registry.registrar.Dashboard.prototype.rotateCall_ = function() {
|
||||
this.x_++;
|
||||
goog.style.setStyle(this.gear_, 'transform', 'rotate(' + this.x_ + 'deg)');
|
||||
if (this.x_ == 360) {
|
||||
this.timer_.stop();
|
||||
}
|
||||
};
|
202
java/google/registry/ui/js/registrar/domain.js
Normal file
|
@ -0,0 +1,202 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
goog.provide('registry.registrar.Domain');
|
||||
|
||||
goog.require('goog.json');
|
||||
goog.require('registry.registrar.XmlResourceComponent');
|
||||
goog.require('registry.soy.forms');
|
||||
goog.require('registry.soy.registrar.domain');
|
||||
goog.require('registry.soy.registrar.domainepp');
|
||||
goog.require('registry.util');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* CRUD for EPP domain objects.
|
||||
* @param {!registry.registrar.Console} console
|
||||
* @constructor
|
||||
* @extends {registry.registrar.XmlResourceComponent}
|
||||
* @final
|
||||
*/
|
||||
registry.registrar.Domain = function(console) {
|
||||
registry.registrar.Domain.base(
|
||||
this, 'constructor',
|
||||
registry.soy.registrar.domain.item,
|
||||
registry.soy.registrar.domainepp,
|
||||
console);
|
||||
};
|
||||
goog.inherits(registry.registrar.Domain,
|
||||
registry.registrar.XmlResourceComponent);
|
||||
|
||||
|
||||
/**
|
||||
* @define {boolean} Launch phase flag. If not SUNRUSH then GA.
|
||||
*/
|
||||
goog.define('registry.registrar.Domain.SUNRUSH', false);
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.Domain.prototype.newModel = function() {
|
||||
var newModel = {item: {'domain:period': ''}};
|
||||
if (registry.registrar.Domain.SUNRUSH) {
|
||||
newModel.allowSmd = true;
|
||||
}
|
||||
return newModel;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Prepare a fetch query for the domain by its domain application id.
|
||||
* @param {!Object} params should have a name field with a
|
||||
* possibly extended domain name id of the form "example.tld:1234"
|
||||
* where the 1234 is the domain application id assigned by the
|
||||
* backend flows.
|
||||
* @override
|
||||
*/
|
||||
registry.registrar.Domain.prototype.prepareFetch = function(params) {
|
||||
var xml;
|
||||
if (registry.registrar.Domain.SUNRUSH) {
|
||||
var idParts = params.id.split(':');
|
||||
if (idParts.length != 2) {
|
||||
registry.util.butter(
|
||||
'Domain queries during sunrush have the form: ' +
|
||||
'"example.tld:1234", where 1234 is the application ID.');
|
||||
throw Error('Invalid domain name for SUNRUSH, lacking application ID');
|
||||
}
|
||||
params.name = idParts[0];
|
||||
params.applicationID = idParts[1];
|
||||
xml = registry.soy.registrar.domainepp.infoSunrush(params);
|
||||
} else {
|
||||
xml = registry.soy.registrar.domainepp.info(params);
|
||||
}
|
||||
return xml.toString();
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.Domain.prototype.processItem = function() {
|
||||
this.model.item = this.model['epp']['response']['resData']['domain:infData'];
|
||||
// Hoist extensions for easy soy access.
|
||||
var extension = this.model['epp']['response']['extension'];
|
||||
if (extension && extension['launch:infData']) {
|
||||
var extendedInfData = extension['launch:infData'];
|
||||
|
||||
var applicationID = extendedInfData['launch:applicationID'];
|
||||
if (applicationID) {
|
||||
this.model.item['launch:applicationID'] = applicationID;
|
||||
}
|
||||
|
||||
var mark = extendedInfData['mark:mark'];
|
||||
if (mark) {
|
||||
this.model.item['mark:mark'] = {'keyValue': goog.json.serialize(mark)};
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap single item into an array.
|
||||
if (this.model.item['domain:ns'] &&
|
||||
this.model.item['domain:ns']['domain:hostObj']) {
|
||||
var hostObj = this.model.item['domain:ns']['domain:hostObj'];
|
||||
if (!goog.isArray(hostObj)) {
|
||||
this.model.item['domain:ns']['domain:hostObj'] = [hostObj];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.Domain.prototype.setupEditor = function(objArgs) {
|
||||
this.typeCounts['contact'] = objArgs.item['domain:contact'] ?
|
||||
objArgs.item['domain:contact'].length : 0;
|
||||
|
||||
var ji = objArgs.item['domain:ns'];
|
||||
this.typeCounts['host'] =
|
||||
ji && ji['domain:hostObj'] ? ji['domain:hostObj'].length : 0;
|
||||
|
||||
this.formInputRowRemovable(
|
||||
document.querySelectorAll('input.domain-hostObj[readonly]'));
|
||||
|
||||
this.formInputRowRemovable(
|
||||
document.querySelectorAll('input.domain-contact[readonly]'));
|
||||
|
||||
this.addRemBtnHandlers(
|
||||
'contact',
|
||||
goog.bind(function() {
|
||||
return 'domain:contact[' + this.typeCounts['contact'] + ']';
|
||||
}, this),
|
||||
goog.bind(function(newFieldName) {
|
||||
return registry.util.renderBeforeRow(
|
||||
'domain-contacts-footer',
|
||||
registry.soy.forms.selectRow, {
|
||||
label: 'Type',
|
||||
name: newFieldName + '.@type',
|
||||
options: ['admin', 'tech', 'billing']
|
||||
});
|
||||
}, this));
|
||||
|
||||
this.addRemBtnHandlers('host', goog.bind(function() {
|
||||
return 'domain:ns.domain:hostObj[' + this.typeCounts['host'] + ']';
|
||||
}, this));
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.Domain.prototype.prepareCreate = function(params) {
|
||||
var form = params.item;
|
||||
params.nextId = form['domain:name'];
|
||||
|
||||
// The presence of this field is used to signal extended template, so remove
|
||||
// if not used.
|
||||
if (form['smd:encodedSignedMark'] == '') {
|
||||
delete form['smd:encodedSignedMark'];
|
||||
}
|
||||
var xml;
|
||||
if (registry.registrar.Domain.SUNRUSH) {
|
||||
xml = registry.soy.registrar.domainepp.createSunrush(params);
|
||||
} else {
|
||||
xml = registry.soy.registrar.domainepp.create(params);
|
||||
}
|
||||
return xml.toString();
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.Domain.prototype.prepareUpdate =
|
||||
function(params) {
|
||||
var form = params.item;
|
||||
var nextId = form['domain:name'];
|
||||
params.nextId = nextId;
|
||||
|
||||
if (form['domain:contact']) {
|
||||
this.addRem(form['domain:contact'], 'Contacts', params);
|
||||
}
|
||||
|
||||
if (form['domain:ns'] && form['domain:ns']['domain:hostObj']) {
|
||||
this.addRem(form['domain:ns']['domain:hostObj'], 'Hosts', params);
|
||||
}
|
||||
|
||||
var xml;
|
||||
if (registry.registrar.Domain.SUNRUSH) {
|
||||
xml = registry.soy.registrar.domainepp.updateSunrush(params);
|
||||
nextId += ':' + form['launch:applicationID'];
|
||||
} else {
|
||||
if (form['domain:ns'] && form['domain:ns']['domain:hostObj']) {
|
||||
this.addRem(form['domain:ns']['domain:hostObj'], 'Hosts', params);
|
||||
}
|
||||
this.addRem(form['domain:contact'], 'Contacts', params);
|
||||
xml = registry.soy.registrar.domainepp.update(params);
|
||||
}
|
||||
|
||||
return xml.toString();
|
||||
};
|
131
java/google/registry/ui/js/registrar/epp_session.js
Normal file
|
@ -0,0 +1,131 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
goog.provide('registry.registrar.EppSession');
|
||||
|
||||
goog.require('goog.Uri');
|
||||
goog.require('registry.Session');
|
||||
goog.require('registry.soy.registrar.epp');
|
||||
goog.require('registry.util');
|
||||
goog.require('registry.xml');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Session state for console.
|
||||
* @param {!registry.registrar.Console} console
|
||||
* @param {string} xsrfToken Populated by server-side soy template.
|
||||
* @param {string} clientId The logged in GAE user.
|
||||
* @constructor
|
||||
* @extends {registry.Session}
|
||||
* @final
|
||||
*/
|
||||
registry.registrar.EppSession = function(console, xsrfToken, clientId) {
|
||||
registry.registrar.EppSession.base(
|
||||
this, 'constructor', new goog.Uri('/registrar-xhr'), xsrfToken,
|
||||
registry.Session.ContentType.EPP);
|
||||
|
||||
/**
|
||||
* @type {!registry.registrar.Console}
|
||||
*/
|
||||
this.console = console;
|
||||
|
||||
/**
|
||||
* @type {!boolean}
|
||||
* @private
|
||||
*/
|
||||
this.isEppLoggedIn_ = false;
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.clientId_ = clientId;
|
||||
};
|
||||
goog.inherits(registry.registrar.EppSession, registry.Session);
|
||||
|
||||
|
||||
/**
|
||||
* Whether the session has received an EPP success response to an EPP
|
||||
* login attempt.
|
||||
* @return {boolean} Whether the user is logged into an EPP session.
|
||||
*/
|
||||
registry.registrar.EppSession.prototype.isEppLoggedIn = function() {
|
||||
return this.isEppLoggedIn_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the clientId if the user is logged in, or throw Error.
|
||||
* @throws Error if the user is not logged in.
|
||||
* @return {string} the clientId.
|
||||
*/
|
||||
registry.registrar.EppSession.prototype.getClientId = function() {
|
||||
return this.clientId_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Login or display butterbar info about error.
|
||||
* @param {function()} successCb to be called on success.
|
||||
*/
|
||||
registry.registrar.EppSession.prototype.login = function(successCb) {
|
||||
var eppArgs = {clId: this.clientId_, clTrid: 'asdf-1235'};
|
||||
this.send(
|
||||
registry.soy.registrar.epp.login(eppArgs).getContent(),
|
||||
goog.bind(function(xml) {
|
||||
var result = xml['epp']['response']['result'];
|
||||
var eppCode = result['@code'];
|
||||
if (eppCode == '1000' || eppCode == '2002') {
|
||||
// Success || Already logged in.
|
||||
this.isEppLoggedIn_ = true;
|
||||
successCb();
|
||||
} else {
|
||||
// Failure.
|
||||
this.isEppLoggedIn_ = false;
|
||||
registry.util.butter('login error: ' + eppCode);
|
||||
}
|
||||
}, this));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logout or display butterbar info about error.
|
||||
*/
|
||||
registry.registrar.EppSession.prototype.logout = function() {
|
||||
this.send(
|
||||
registry.soy.registrar.epp.logout(
|
||||
{clTrid: 'asdf-1235'}).getContent(),
|
||||
goog.bind(function(xml) {
|
||||
var result = xml['epp']['response']['result'];
|
||||
var eppCode = result['@code'];
|
||||
registry.util.butter(
|
||||
'logout ' + eppCode + ': ' + result['msg']['keyValue']);
|
||||
// Going to be safe here and force a login either way.
|
||||
this.isEppLoggedIn_ = false;
|
||||
}, this));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Send xml to the server.
|
||||
* @param {string} xml Request document.
|
||||
* @param {function(!Object)} callback For XhrIo result throws.
|
||||
*/
|
||||
registry.registrar.EppSession.prototype.send = function(xml, callback) {
|
||||
var toXmlJsonCb = function(rspXml) {
|
||||
callback(registry.xml.convertToJson(rspXml));
|
||||
};
|
||||
this.sendXhrIo(xml, toXmlJsonCb);
|
||||
};
|
103
java/google/registry/ui/js/registrar/host.js
Normal file
|
@ -0,0 +1,103 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
goog.provide('registry.registrar.Host');
|
||||
|
||||
goog.require('registry.registrar.XmlResourceComponent');
|
||||
goog.require('registry.soy.registrar.host');
|
||||
goog.require('registry.soy.registrar.hostepp');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* CRUD for EPP host objects.
|
||||
* @param {!registry.registrar.Console} console
|
||||
* @constructor
|
||||
* @extends {registry.registrar.XmlResourceComponent}
|
||||
* @final
|
||||
*/
|
||||
registry.registrar.Host = function(console) {
|
||||
registry.registrar.Host.base(
|
||||
this, 'constructor',
|
||||
registry.soy.registrar.host.item,
|
||||
registry.soy.registrar.hostepp,
|
||||
console);
|
||||
};
|
||||
goog.inherits(registry.registrar.Host,
|
||||
registry.registrar.XmlResourceComponent);
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.Host.prototype.processItem = function() {
|
||||
this.model.item = this.model['epp']['response']['resData']['host:infData'];
|
||||
if (this.model.item['host:addr']) {
|
||||
if (!goog.isArray(this.model.item['host:addr'])) {
|
||||
this.model.item['host:addr'] = [this.model.item['host:addr']];
|
||||
}
|
||||
} else {
|
||||
this.model.item['host:addr'] = [];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.Host.prototype.setupEditor = function(objArgs) {
|
||||
this.typeCounts['host-addr'] =
|
||||
objArgs.item['host:addr'] ? objArgs.item['host:addr'].length : 0;
|
||||
this.addRemBtnHandlers('host-addr', goog.bind(function() {
|
||||
return 'host:addr[' + this.typeCounts['host-addr'] + ']';
|
||||
}, this));
|
||||
|
||||
this.formInputRowRemovable(document.querySelectorAll('input[readonly]'));
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.Host.prototype.prepareCreate = function(params) {
|
||||
params.nextId = params.item['host:name'];
|
||||
return registry.soy.registrar.hostepp.create(params).toString();
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.Host.prototype.prepareUpdate = function(params) {
|
||||
var form = params.item;
|
||||
var addAddrs = [];
|
||||
var remAddrs = [];
|
||||
if (form['host:addr']) {
|
||||
var oldAddrs = form['host:oldAddr'] || [];
|
||||
var newAddrs = form['host:addr'];
|
||||
var length = Math.max(oldAddrs.length, newAddrs.length);
|
||||
for (var i = 0; i < length; i++) {
|
||||
if (i >= oldAddrs.length) {
|
||||
addAddrs.push(newAddrs[i]['value']);
|
||||
} else if (i >= newAddrs.length) {
|
||||
remAddrs.push(oldAddrs[i]['value']);
|
||||
} else {
|
||||
if (newAddrs[i]['value'] == oldAddrs[i]['value']) {
|
||||
// Do nothing.
|
||||
} else if (newAddrs[i]['value'] == '') {
|
||||
remAddrs.push(oldAddrs[i]['value']);
|
||||
} else {
|
||||
remAddrs.push(oldAddrs[i]['value']);
|
||||
addAddrs.push(newAddrs[i]['value']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
params.addAddrs = addAddrs;
|
||||
params.remAddrs = remAddrs;
|
||||
params.nextId = form['host:chgName'];
|
||||
return registry.soy.registrar.hostepp.update(params).toString();
|
||||
};
|
35
java/google/registry/ui/js/registrar/main.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
/**
|
||||
* @fileoverview Entry point for the registrar console.
|
||||
*/
|
||||
|
||||
goog.provide('registry.registrar.main');
|
||||
|
||||
goog.require('registry.registrar.Console');
|
||||
|
||||
|
||||
/**
|
||||
* Instantiates a registry object, which syncs with the server.
|
||||
* Sub-templates are invoked based on location/pathname, to choose
|
||||
* either Registry or Registrar pages.
|
||||
*
|
||||
* @param {string} xsrfToken populated by server-side soy template.
|
||||
* @param {string} clientId The registrar clientId.
|
||||
* @export
|
||||
*/
|
||||
registry.registrar.main = function(xsrfToken, clientId) {
|
||||
new registry.registrar.Console(xsrfToken, clientId);
|
||||
};
|
412
java/google/registry/ui/js/registrar/payment.js
Normal file
|
@ -0,0 +1,412 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
goog.provide('registry.registrar.Payment');
|
||||
|
||||
goog.require('goog.Uri');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.json');
|
||||
goog.require('goog.object');
|
||||
goog.require('goog.soy');
|
||||
goog.require('registry.Component');
|
||||
goog.require('registry.MenuButton');
|
||||
goog.require('registry.Session');
|
||||
goog.require('registry.forms');
|
||||
goog.require('registry.registrar.BrainFrame');
|
||||
goog.require('registry.soy.registrar.console');
|
||||
goog.require('registry.soy.registrar.payment');
|
||||
goog.require('registry.util');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Page allowing registrar to send money to registry.
|
||||
*
|
||||
* <p>This page contains a form that asks the user to enter an arbitrary amount
|
||||
* and a payment method, which can be credit card or PayPal. Multiple
|
||||
* currencies are supported.
|
||||
*
|
||||
* <h3>PCI Compliance</h3>
|
||||
*
|
||||
* <p>We don't have any access whatsoever to the credit card information. We
|
||||
* embed an iframe run by Braintree Payments. The user can then provide his
|
||||
* credit card (or PayPal) details directly to Braintree. Braintree then gives
|
||||
* us a nonce value representing the payment method, which we can use to issue
|
||||
* the transaction.
|
||||
*
|
||||
* <h3>Bidirectional Protection</h3>
|
||||
*
|
||||
* <p>To use Braintree's iframe, we need to load a script from their server. We
|
||||
* don't want that script to have access to the Registrar Console. If Braintree
|
||||
* got pwnd, the attacker would be able to issue EPP commands as a registrar.
|
||||
*
|
||||
* <p>We fix this problem by embedding the Braintree iframe inside another
|
||||
* sandbox iframe that's hosted from a Cloud Storage bucket. This frame is
|
||||
* defined by {@code brainframe.html}. It's basically an empty shell that sends
|
||||
* a request back to the production environment for {@code brainframe.js}.
|
||||
*
|
||||
* <p>The importance of the Cloud Storage bucket is that it is served from a
|
||||
* separate domain. This causes the browser to forbid the iframe from accessing
|
||||
* the contents of the parent frame. The HTML5 {@code sandbox} attribute does
|
||||
* this too, but we can't use it, because the Venmo functionality in the
|
||||
* Braintree JS SDK needs to be able to access {@code document.cookie}, which
|
||||
* is forbidden in a sandbox environment. This HTML5 {@code sandbox} feature is
|
||||
* also not available in older versions of Internet Explorer.
|
||||
*
|
||||
* <h3>Business Logic</h3>
|
||||
*
|
||||
* <p>This page starts off as a loading glyph, while we issue an RPC to the
|
||||
* backend. We ask for a Braintree token, which currencies are available, and
|
||||
* the location of the brainframe HTML file. Once we get that data, we render
|
||||
* the form.
|
||||
*
|
||||
* <p>Once the sandbox iframe inside that form has loaded, it'll send us a
|
||||
* message asking for the token. We give it the token, which it uses to load
|
||||
* the Braintree iframe.
|
||||
*
|
||||
* <p>To make sure the sandbox iframe is the same size as the Braintree iframe,
|
||||
* the sandbox iframe will send us messages on occasion asking to be resized.
|
||||
*
|
||||
* <p>The disabled state of the submit button is managed judiciously. It's
|
||||
* disabled initially, until we get a READY message from the sandbox iframe,
|
||||
* indicating that the Braintree iframe is fully loaded. We also disable the
|
||||
* submit button during the submit process.
|
||||
*
|
||||
* <p>When the user presses the submit button, we send a message to the sandbox
|
||||
* iframe asking it to submit the Braintree iframe. When the Braintree iframe
|
||||
* is submitted, it gives the sandbox iframe the the payment method nonce,
|
||||
* which it passes along to us. Then we pass the form data to the backend via
|
||||
* the payment RPC, which invokes the Braintree Java API to issue the
|
||||
* transaction.
|
||||
*
|
||||
* <p>If the payment RPC fails, we'll either show an error on a field, or in a
|
||||
* bloody butterbar. If it succeeds, then the backend will give us the
|
||||
* transaction ID assigned by Braintree, which we then render on a success
|
||||
* page.
|
||||
*
|
||||
* <p>The success page contains a "Make Another Payment" button which, if
|
||||
* clicked, will reset the state of this page back to the beginning.
|
||||
*
|
||||
* @param {!registry.registrar.Console} console
|
||||
* @param {string} xsrfToken Security token to pass back to the server.
|
||||
* @constructor
|
||||
* @extends {registry.Component}
|
||||
* @final
|
||||
*/
|
||||
registry.registrar.Payment = function(console, xsrfToken) {
|
||||
registry.registrar.Payment.base(this, 'constructor', console);
|
||||
|
||||
/**
|
||||
* Element in which this page is rendered.
|
||||
* @private {!Element}
|
||||
* @const
|
||||
*/
|
||||
this.content_ = goog.dom.getRequiredElement('reg-content');
|
||||
|
||||
/**
|
||||
* Braintree API nonce token generated by the backend. This value is a
|
||||
* prerequisite to rendering the Braintree iframe.
|
||||
* @private {string}
|
||||
*/
|
||||
this.token_ = '';
|
||||
|
||||
/**
|
||||
* Braintree API nonce value for payment method selected by user.
|
||||
* @private {string}
|
||||
*/
|
||||
this.paymentMethodNonce_ = '';
|
||||
|
||||
/**
|
||||
* Currency drop-down widget in form.
|
||||
* @private {?registry.MenuButton}
|
||||
*/
|
||||
this.currencyMenu_ = null;
|
||||
|
||||
/**
|
||||
* XHR client to {@code RegistrarPaymentSetupAction}.
|
||||
* @private {!registry.Session.<!registry.rpc.PaymentSetup.Request,
|
||||
* !registry.rpc.PaymentSetup.Response>}
|
||||
* @const
|
||||
*/
|
||||
this.setupRpc_ =
|
||||
new registry.Session(new goog.Uri('/registrar-payment-setup'),
|
||||
xsrfToken,
|
||||
registry.Session.ContentType.JSON);
|
||||
|
||||
/**
|
||||
* XHR client to {@code RegistrarPaymentAction}.
|
||||
* @private {!registry.Session.<!registry.rpc.Payment.Request,
|
||||
* !registry.rpc.Payment.Response>}
|
||||
* @const
|
||||
*/
|
||||
this.paymentRpc_ =
|
||||
new registry.Session(new goog.Uri('/registrar-payment'),
|
||||
xsrfToken,
|
||||
registry.Session.ContentType.JSON);
|
||||
|
||||
this.listen(goog.global.window,
|
||||
goog.events.EventType.MESSAGE,
|
||||
this.onMessage_);
|
||||
};
|
||||
goog.inherits(registry.registrar.Payment, registry.Component);
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.Payment.prototype.bindToDom = function(id) {
|
||||
registry.registrar.Payment.base(this, 'bindToDom', id);
|
||||
if (!goog.isNull(goog.dom.getElement('reg-app-buttons'))) {
|
||||
goog.dom.removeChildren(goog.dom.getElement('reg-app-buttons'));
|
||||
}
|
||||
if (!registry.registrar.Payment.isBrowserSupported_()) {
|
||||
goog.soy.renderElement(this.content_,
|
||||
registry.soy.registrar.payment.unsupported);
|
||||
return;
|
||||
}
|
||||
goog.soy.renderElement(this.content_, registry.soy.registrar.console.loading);
|
||||
this.setupRpc_.sendXhrIo({}, goog.bind(this.onSetup_, this));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler invoked when we receive information from our backend, such as a
|
||||
* Braintree token, which is necessary for us to render the payment form.
|
||||
* @param {!registry.rpc.PaymentSetup.Response} response
|
||||
* @private
|
||||
*/
|
||||
registry.registrar.Payment.prototype.onSetup_ = function(response) {
|
||||
if (response.status != 'SUCCESS') {
|
||||
if (response.message == 'not-using-cc-billing') {
|
||||
goog.soy.renderElement(this.content_,
|
||||
registry.soy.registrar.payment.notUsingCcBilling);
|
||||
} else {
|
||||
registry.forms.displayError(response.message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var result = response.results[0];
|
||||
this.token_ = result.token;
|
||||
this.paymentMethodNonce_ = '';
|
||||
goog.soy.renderElement(this.content_,
|
||||
registry.soy.registrar.payment.form,
|
||||
result);
|
||||
this.listen(
|
||||
goog.dom.getRequiredElementByClass(goog.getCssName('reg-payment-form')),
|
||||
goog.events.EventType.SUBMIT,
|
||||
this.onSubmit_);
|
||||
this.currencyMenu_ =
|
||||
new registry.MenuButton(goog.dom.getRequiredElement('currency'));
|
||||
this.registerDisposable(this.currencyMenu_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler invoked when payment form is submitted.
|
||||
* @param {!goog.events.BrowserEvent} e
|
||||
* @private
|
||||
*/
|
||||
registry.registrar.Payment.prototype.onSubmit_ = function(e) {
|
||||
e.preventDefault();
|
||||
this.submit_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Submits payment form.
|
||||
* @private
|
||||
*/
|
||||
registry.registrar.Payment.prototype.submit_ = function() {
|
||||
registry.forms.resetErrors();
|
||||
registry.registrar.Payment.setEnabled_(false);
|
||||
if (this.paymentMethodNonce_ == '') {
|
||||
this.send_(
|
||||
'type', registry.registrar.BrainFrame.MessageType.SUBMIT_REQUEST);
|
||||
return;
|
||||
}
|
||||
this.paymentRpc_.sendXhrIo(
|
||||
{
|
||||
amount: goog.dom.getRequiredElement('amount').value,
|
||||
currency: this.currencyMenu_.getValue(),
|
||||
paymentMethodNonce: this.paymentMethodNonce_
|
||||
},
|
||||
goog.bind(this.onPayment_, this));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback for backend payment RPC that issues the transaction.
|
||||
* @param {!registry.rpc.Payment.Response} response
|
||||
* @private
|
||||
*/
|
||||
registry.registrar.Payment.prototype.onPayment_ = function(response) {
|
||||
registry.registrar.Payment.setEnabled_(true);
|
||||
if (response.status != 'SUCCESS') {
|
||||
registry.forms.displayError(response.message, response.field);
|
||||
return;
|
||||
}
|
||||
goog.soy.renderElement(this.content_,
|
||||
registry.soy.registrar.payment.success,
|
||||
response.results[0]);
|
||||
this.listenOnce(
|
||||
goog.dom.getRequiredElementByClass(goog.getCssName('reg-payment-again')),
|
||||
goog.events.EventType.CLICK,
|
||||
this.bindToDom);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler invoked when {@code brainframe.js} sends us a message.
|
||||
* @param {!goog.events.BrowserEvent} e
|
||||
* @private
|
||||
*/
|
||||
registry.registrar.Payment.prototype.onMessage_ = function(e) {
|
||||
var msg = /** @type {!MessageEvent.<string>} */ (e.getBrowserEvent());
|
||||
var brainframe =
|
||||
goog.dom.getElementByClass(goog.getCssName('reg-payment-form-method'));
|
||||
if (brainframe == null ||
|
||||
msg.source != goog.dom.getFrameContentWindow(brainframe)) {
|
||||
return;
|
||||
}
|
||||
var data;
|
||||
try {
|
||||
data = goog.json.parse(msg.data);
|
||||
} catch (ex) {
|
||||
// TODO(b/26876003): Figure out why it's possible that the Braintree iframe
|
||||
// is able to propagate messages up to our level.
|
||||
registry.util.log(ex, msg.source, msg.data);
|
||||
return;
|
||||
}
|
||||
switch (goog.object.get(data, 'type')) {
|
||||
case registry.registrar.BrainFrame.MessageType.TOKEN_REQUEST:
|
||||
goog.asserts.assert(this.token_ != '');
|
||||
this.send_(
|
||||
'type', registry.registrar.BrainFrame.MessageType.TOKEN_RESPONSE,
|
||||
'token', this.token_);
|
||||
break;
|
||||
case registry.registrar.BrainFrame.MessageType.RESIZE_REQUEST:
|
||||
brainframe.height = goog.object.get(data, 'height');
|
||||
break;
|
||||
case registry.registrar.BrainFrame.MessageType.READY:
|
||||
registry.registrar.Payment.setEnabled_(true);
|
||||
break;
|
||||
case registry.registrar.BrainFrame.MessageType.SUBMIT_ERROR:
|
||||
registry.registrar.Payment.setEnabled_(true);
|
||||
registry.forms.displayError(goog.object.get(data, 'message'), 'method');
|
||||
break;
|
||||
case registry.registrar.BrainFrame.MessageType.PAYMENT_METHOD:
|
||||
registry.registrar.Payment.setEnabled_(true);
|
||||
this.setPaymentMethod_(
|
||||
/** @type {!braintreepayments.PaymentMethod} */ (
|
||||
goog.object.get(data, 'method')));
|
||||
if (goog.object.get(data, 'submit')) {
|
||||
this.submit_();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw Error('Unexpected message: ' + msg.data);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Updates UI to display selected payment method.
|
||||
*
|
||||
* <p>We remove the iframe from the page as soon as this happens, because the
|
||||
* UI would be busted otherwise. The Braintree UI for changing the payment
|
||||
* method (after it's been entered) does not appear to stop respond to submit
|
||||
* events. It also causes ugly scroll bars to appear inside the iframe.
|
||||
*
|
||||
* <p>This approach is also advantageous for screenshot testing. We do not want
|
||||
* our continuous integration testing system to talk to Braintree's servers. So
|
||||
* we mock out the brainframe with {@code integration-test-brainframe.html}
|
||||
* which <i>only</i> sends us a METHOD message, which we then render ourselves.
|
||||
*
|
||||
* @param {!braintreepayments.PaymentMethod} pm
|
||||
* @private
|
||||
*/
|
||||
registry.registrar.Payment.prototype.setPaymentMethod_ = function(pm) {
|
||||
registry.forms.resetErrors();
|
||||
goog.dom.removeNode(
|
||||
goog.dom.getElementByClass(goog.getCssName('reg-payment-form-method')));
|
||||
var paymentMethodInfoBox =
|
||||
goog.dom.getRequiredElementByClass(
|
||||
goog.getCssName('reg-payment-form-method-info'));
|
||||
switch (pm.type) {
|
||||
case 'CreditCard':
|
||||
goog.soy.renderElement(
|
||||
paymentMethodInfoBox,
|
||||
registry.soy.registrar.payment.methodInfoCard,
|
||||
{cardType: pm.details.cardType, lastTwo: pm.details.lastTwo});
|
||||
break;
|
||||
case 'PayPalAccount':
|
||||
goog.soy.renderElement(
|
||||
paymentMethodInfoBox,
|
||||
registry.soy.registrar.payment.methodInfoPaypal,
|
||||
{email: pm.details.email});
|
||||
break;
|
||||
default:
|
||||
throw Error('Unknown payment method: ' + pm.type);
|
||||
}
|
||||
registry.util.setVisible(paymentMethodInfoBox, true);
|
||||
this.paymentMethodNonce_ = pm.nonce;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends message to brainframe.
|
||||
* @param {...*} var_args Passed along to {@code goog.object.create}.
|
||||
* @private
|
||||
*/
|
||||
registry.registrar.Payment.prototype.send_ = function(var_args) {
|
||||
goog.asserts.assert(arguments[0] == 'type');
|
||||
var brainframeWindow =
|
||||
goog.dom.getFrameContentWindow(
|
||||
goog.dom.getRequiredElementByClass(
|
||||
goog.getCssName('reg-payment-form-method')));
|
||||
// We send a string value to support older versions of IE.
|
||||
brainframeWindow.postMessage(
|
||||
goog.json.serialize(goog.object.create.apply(null, arguments)),
|
||||
'*');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enables submit button and hides mini loading glyph.
|
||||
* @param {boolean} enabled
|
||||
* @private
|
||||
*/
|
||||
registry.registrar.Payment.setEnabled_ = function(enabled) {
|
||||
registry.forms.setEnabled(
|
||||
goog.dom.getRequiredElementByClass(
|
||||
goog.getCssName('reg-payment-form-submit')), enabled);
|
||||
registry.util.setVisible(
|
||||
goog.dom.getRequiredElementByClass(
|
||||
goog.getCssName('reg-payment-form-loader')), !enabled);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns {@code true} if browser has all the features we need.
|
||||
* @return {boolean}
|
||||
* @private
|
||||
* @see "http://caniuse.com/#feat=dispatchevent"
|
||||
*/
|
||||
registry.registrar.Payment.isBrowserSupported_ = function() {
|
||||
// dispatchEvent is used by brainframe.js and is IE 9+.
|
||||
return goog.object.containsKey(
|
||||
goog.dom.createElement(goog.dom.TagName.FORM),
|
||||
'dispatchEvent');
|
||||
};
|
50
java/google/registry/ui/js/registrar/resources.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
goog.provide('registry.registrar.Resources');
|
||||
|
||||
goog.require('goog.Uri');
|
||||
goog.require('goog.dom');
|
||||
goog.require('registry.Resource');
|
||||
goog.require('registry.ResourceComponent');
|
||||
goog.require('registry.soy.registrar.console');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Resources and billing page.
|
||||
* @param {!registry.registrar.Console} console
|
||||
* @param {string} xsrfToken Security token to pass back to the server.
|
||||
* @constructor
|
||||
* @extends {registry.ResourceComponent}
|
||||
* @final
|
||||
*/
|
||||
registry.registrar.Resources = function(console, xsrfToken) {
|
||||
registry.registrar.Resources.base(
|
||||
this,
|
||||
'constructor',
|
||||
console,
|
||||
new registry.Resource(new goog.Uri('/registrar-settings'), xsrfToken),
|
||||
registry.soy.registrar.console.resources,
|
||||
null);
|
||||
};
|
||||
goog.inherits(registry.registrar.Resources,
|
||||
registry.ResourceComponent);
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.Resources.prototype.bindToDom = function(id) {
|
||||
registry.registrar.Resources.base(this, 'bindToDom', '');
|
||||
goog.dom.removeChildren(goog.dom.getRequiredElement('reg-app-buttons'));
|
||||
};
|
110
java/google/registry/ui/js/registrar/security_settings.js
Normal file
|
@ -0,0 +1,110 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
goog.provide('registry.registrar.SecuritySettings');
|
||||
|
||||
goog.require('goog.Uri');
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.classlist');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.soy');
|
||||
goog.require('registry.Resource');
|
||||
goog.require('registry.ResourceComponent');
|
||||
goog.require('registry.soy.registrar.security');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Security Settings page.
|
||||
* @param {!registry.registrar.Console} console
|
||||
* @param {string} xsrfToken Security token to pass back to the server.
|
||||
* @constructor
|
||||
* @extends {registry.ResourceComponent}
|
||||
* @final
|
||||
*/
|
||||
registry.registrar.SecuritySettings = function(console, xsrfToken) {
|
||||
registry.registrar.SecuritySettings.base(
|
||||
this,
|
||||
'constructor',
|
||||
console,
|
||||
new registry.Resource(new goog.Uri('/registrar-settings'), xsrfToken),
|
||||
registry.soy.registrar.security.settings,
|
||||
null);
|
||||
};
|
||||
goog.inherits(registry.registrar.SecuritySettings, registry.ResourceComponent);
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.SecuritySettings.prototype.bindToDom = function(id) {
|
||||
registry.registrar.SecuritySettings.base(this, 'bindToDom', 'fake');
|
||||
goog.dom.removeNode(goog.dom.getRequiredElement('reg-app-btn-back'));
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.SecuritySettings.prototype.setupEditor =
|
||||
function(objArgs) {
|
||||
goog.dom.classlist.add(goog.dom.getRequiredElement('ips'),
|
||||
goog.getCssName('editing'));
|
||||
var ips = goog.dom.getElementsByClass(goog.getCssName('ip'),
|
||||
goog.dom.getRequiredElement('ips'));
|
||||
goog.array.forEach(ips, function(ip) {
|
||||
var remBtn = goog.dom.getChildren(ip)[0];
|
||||
goog.events.listen(remBtn,
|
||||
goog.events.EventType.CLICK,
|
||||
goog.bind(this.onIpRemove_, this, remBtn));
|
||||
}, this);
|
||||
this.typeCounts['reg-ips'] = objArgs.ipAddressWhitelist ?
|
||||
objArgs.ipAddressWhitelist.length : 0;
|
||||
|
||||
goog.events.listen(goog.dom.getRequiredElement('btn-add-ip'),
|
||||
goog.events.EventType.CLICK,
|
||||
this.onIpAdd_,
|
||||
false,
|
||||
this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Click handler for IP add button.
|
||||
* @private
|
||||
*/
|
||||
registry.registrar.SecuritySettings.prototype.onIpAdd_ = function() {
|
||||
var ipInputElt = goog.dom.getRequiredElement('newIp');
|
||||
var ipElt = goog.soy.renderAsFragment(registry.soy.registrar.security.ip, {
|
||||
name: 'ipAddressWhitelist[' + this.typeCounts['reg-ips'] + ']',
|
||||
ip: ipInputElt.value
|
||||
});
|
||||
goog.dom.appendChild(goog.dom.getRequiredElement('ips'), ipElt);
|
||||
var remBtn = goog.dom.getFirstElementChild(ipElt);
|
||||
goog.dom.classlist.remove(remBtn, goog.getCssName('hidden'));
|
||||
goog.events.listen(remBtn, goog.events.EventType.CLICK,
|
||||
goog.bind(this.onIpRemove_, this, remBtn));
|
||||
this.typeCounts['reg-ips']++;
|
||||
ipInputElt.value = '';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Click handler for IP remove button.
|
||||
* @param {!Element} remBtn The remove button.
|
||||
* @private
|
||||
*/
|
||||
registry.registrar.SecuritySettings.prototype.onIpRemove_ =
|
||||
function(remBtn) {
|
||||
goog.dom.removeNode(goog.dom.getParentElement(remBtn));
|
||||
this.typeCounts['reg-ips']--;
|
||||
};
|
49
java/google/registry/ui/js/registrar/whois_settings.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
goog.provide('registry.registrar.WhoisSettings');
|
||||
|
||||
goog.require('goog.Uri');
|
||||
goog.require('goog.dom');
|
||||
goog.require('registry.Resource');
|
||||
goog.require('registry.ResourceComponent');
|
||||
goog.require('registry.soy.registrar.whois');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* WHOIS Settings page.
|
||||
* @param {!registry.registrar.Console} console
|
||||
* @param {string} xsrfToken Cross-site request forgery protection token.
|
||||
* @constructor
|
||||
* @extends {registry.ResourceComponent}
|
||||
* @final
|
||||
*/
|
||||
registry.registrar.WhoisSettings = function(console, xsrfToken) {
|
||||
registry.registrar.WhoisSettings.base(
|
||||
this,
|
||||
'constructor',
|
||||
console,
|
||||
new registry.Resource(new goog.Uri('/registrar-settings'), xsrfToken),
|
||||
registry.soy.registrar.whois.settings,
|
||||
null);
|
||||
};
|
||||
goog.inherits(registry.registrar.WhoisSettings, registry.ResourceComponent);
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.WhoisSettings.prototype.bindToDom = function(id) {
|
||||
registry.registrar.WhoisSettings.base(this, 'bindToDom', 'fake');
|
||||
goog.dom.removeNode(goog.dom.getRequiredElement('reg-app-btn-back'));
|
||||
};
|
217
java/google/registry/ui/js/registrar/xml_resource_component.js
Normal file
|
@ -0,0 +1,217 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
goog.provide('registry.registrar.XmlResourceComponent');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.dom.classlist');
|
||||
goog.require('registry.EditItem');
|
||||
goog.require('registry.util');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The ResourceComponent class respresents server state for a named
|
||||
* resource and binds UI CRUD operations on it, or its constituent
|
||||
* collection.
|
||||
* @param {function()} itemTmpl
|
||||
* @param {!Object} eppTmpls Epp xml templates for info requests.
|
||||
* @param {!registry.registrar.Console} console
|
||||
* @constructor
|
||||
* @extends {registry.EditItem}
|
||||
*/
|
||||
registry.registrar.XmlResourceComponent = function(
|
||||
itemTmpl, eppTmpls, console) {
|
||||
registry.registrar.XmlResourceComponent.base(
|
||||
this, 'constructor', console, itemTmpl);
|
||||
|
||||
/** @type {!Object} */
|
||||
this.eppTmpls = eppTmpls;
|
||||
};
|
||||
goog.inherits(registry.registrar.XmlResourceComponent, registry.EditItem);
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.XmlResourceComponent.prototype.bindToDom =
|
||||
function(id) {
|
||||
// XXX: EPP resources still use null state.
|
||||
registry.registrar.XmlResourceComponent.base(
|
||||
this, 'bindToDom', (id || ''));
|
||||
if (id) {
|
||||
this.fetchItem(id);
|
||||
} else {
|
||||
// Start edit of empty object.
|
||||
this.edit();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.XmlResourceComponent.prototype.fetchItem = function(id) {
|
||||
var queryParams = {id: id, clTrid: 'abc-1234'};
|
||||
var xml = this.prepareFetch(queryParams);
|
||||
this.console.session.send(xml, goog.bind(this.handleFetchItem, this, id));
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.XmlResourceComponent.prototype.handleFetchItem =
|
||||
function(id, rsp) {
|
||||
this.model = rsp;
|
||||
var result = rsp['epp']['response']['result'];
|
||||
var resCode = result['@code'];
|
||||
// XXX: Should use enum.
|
||||
if (resCode == 1000) { // OK
|
||||
this.model.readonly = true;
|
||||
this.processItem();
|
||||
this.renderItem(this.model);
|
||||
} else if (resCode == 2303) { // Missing
|
||||
registry.util.butter(
|
||||
'Could not find: "' + id + '". Please enter as "type/id"');
|
||||
} else {
|
||||
// includes general failure.
|
||||
registry.util.butter(resCode + ':' + result['msg']['keyValue']);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sublcasses should override to populate create queryParams with form
|
||||
* fields as needed. {@code queryParams.nextId} MUST be set to the
|
||||
* new object's ID.
|
||||
* @param {!Object} queryParams
|
||||
*/
|
||||
registry.registrar.XmlResourceComponent.prototype.prepareCreate =
|
||||
goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Calls prepareCreate with template params and then send the returned
|
||||
* XML to the server
|
||||
* @override
|
||||
*/
|
||||
registry.registrar.XmlResourceComponent.prototype.sendCreate = function() {
|
||||
var form = registry.util.parseForm('item');
|
||||
var queryParams = {item: form, clTrid: 'abc-1234'};
|
||||
var xml = this.prepareCreate(queryParams);
|
||||
this.nextId = queryParams.nextId;
|
||||
this.console.session.send(xml, goog.bind(this.handleUpdateResponse, this));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calls prepareUpdate with template params and then send the returned
|
||||
* XML to the server.
|
||||
* @override
|
||||
*/
|
||||
registry.registrar.XmlResourceComponent.prototype.sendUpdate = function() {
|
||||
var form = registry.util.parseForm('item');
|
||||
var queryParams = {item: form, clTrid: 'abc-1234'};
|
||||
var xml = this.prepareUpdate(queryParams);
|
||||
this.nextId = queryParams.nextId;
|
||||
this.console.session.send(xml, goog.bind(this.handleUpdateResponse, this));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sublcasses should override to populate fetch queryParams with form
|
||||
* fields as needed.
|
||||
* @param {!Object} queryParams
|
||||
* @return {string} EPP xml string
|
||||
*/
|
||||
registry.registrar.XmlResourceComponent.prototype.prepareFetch =
|
||||
function(queryParams) {
|
||||
return this.eppTmpls.info(queryParams).toString();
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.registrar.XmlResourceComponent.prototype.handleUpdateResponse =
|
||||
function(rsp) {
|
||||
var result = rsp['epp']['response']['result'];
|
||||
if (result['@code'] == 1000) {
|
||||
// XXX: Consider timer, probably just as a seconds arg with impl in butter.
|
||||
registry.util.butter('Saved.');
|
||||
this.fetchItem(this.nextId || '');
|
||||
this.nextId = null;
|
||||
this.toggleEdit();
|
||||
} else {
|
||||
registry.util.butter(result['msg']['keyValue']);
|
||||
}
|
||||
return rsp;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper to add add/remove hosts/contacts on queryParams:
|
||||
*
|
||||
* queryParams[op + fieldType] = formFields.
|
||||
*
|
||||
* @param {Array.<Object>} formFields named form item representations
|
||||
* that have an associated {op: (add|rem)} attribute.
|
||||
* @param {string} fieldType capitalized pluralized type name, e.g. "Contacts".
|
||||
* @param {!Object} queryParams
|
||||
*/
|
||||
registry.registrar.XmlResourceComponent.prototype.addRem =
|
||||
function(formFields, fieldType, queryParams) {
|
||||
var add = [];
|
||||
var rem = [];
|
||||
for (var i = 0; i < formFields.length; i++) {
|
||||
var formField = formFields[i];
|
||||
var op = formField['op'];
|
||||
switch (op) {
|
||||
case 'add':
|
||||
add.push(formField);
|
||||
break;
|
||||
case 'rem':
|
||||
rem.push(formField);
|
||||
break;
|
||||
default:
|
||||
registry.util.log(
|
||||
'Unknown op attribute ' + op + ' on form field:',
|
||||
formField);
|
||||
}
|
||||
}
|
||||
if (add.length > 0) {
|
||||
queryParams['add' + fieldType] = add;
|
||||
}
|
||||
if (rem.length > 0) {
|
||||
queryParams['rem' + fieldType] = rem;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Make the given form input rows removable.
|
||||
* @param {NodeList} elts array of input elements.
|
||||
* @param {function()=} opt_onclickCb called when remove button is clicked.
|
||||
*/
|
||||
registry.registrar.XmlResourceComponent.prototype.formInputRowRemovable =
|
||||
function(elts, opt_onclickCb) {
|
||||
for (var i = 0; i < elts.length; i++) {
|
||||
var parent = goog.dom.getParentElement(elts[i]);
|
||||
this.appendRemoveBtn(parent, goog.partial(function(elt) {
|
||||
var eppRemInput = goog.dom.createDom(goog.dom.TagName.INPUT, {
|
||||
'name': elt.name.replace(/\.value/, '.op'),
|
||||
'value': 'rem',
|
||||
'type': 'hidden'
|
||||
});
|
||||
goog.dom.appendChild(goog.dom.getParentElement(elt), eppRemInput);
|
||||
goog.dom.classlist.add(
|
||||
goog.dom.getParentElement(
|
||||
goog.dom.getParentElement(elt)), goog.getCssName('hidden'));
|
||||
}, elts[i]));
|
||||
}
|
||||
};
|
92
java/google/registry/ui/js/resource.js
Normal file
|
@ -0,0 +1,92 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
goog.provide('registry.Resource');
|
||||
|
||||
goog.require('goog.json');
|
||||
goog.require('registry.Session');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Provide a CRUD view of a server resource.
|
||||
*
|
||||
* @param {!goog.Uri} baseUri Target RESTful resource.
|
||||
* @param {string} xsrfToken Security token to pass back to the server.
|
||||
* @extends {registry.Session}
|
||||
* @constructor
|
||||
*/
|
||||
registry.Resource = function(baseUri, xsrfToken) {
|
||||
registry.Resource.base(this, 'constructor', baseUri, xsrfToken,
|
||||
registry.Session.ContentType.JSON);
|
||||
};
|
||||
goog.inherits(registry.Resource, registry.Session);
|
||||
|
||||
|
||||
/**
|
||||
* Get the resource from the server.
|
||||
*
|
||||
* @param {!Object} args Params for server. Do not set the 'op' field on args.
|
||||
* @param {!Function} callback For retrieved resource.
|
||||
*/
|
||||
registry.Resource.prototype.read = function(args, callback) {
|
||||
this.send_('read', args, callback);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create the resource on the server.
|
||||
*
|
||||
* @param {!Object} args params for server. Do not set the 'op' field on args.
|
||||
* @param {!Function} callback on success.
|
||||
* @param {string} newId name for the new resource.
|
||||
* @throws {!Exception} if the 'op' field is set on args.
|
||||
*/
|
||||
registry.Resource.prototype.create = function(args, callback, newId) {
|
||||
this.send_('create', args, callback, newId);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create the resource on the server.
|
||||
*
|
||||
* @param {!Object} args params for server. Do not set the 'op' field on args.
|
||||
* @param {!Function} callback on success.
|
||||
* @throws {!Exception} if the 'op' field is set on args.
|
||||
*/
|
||||
registry.Resource.prototype.update = function(args, callback) {
|
||||
this.send_('update', args, callback);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* RESTful access to resources on the server.
|
||||
*
|
||||
* @param {string} opCode One of (create|read|update)
|
||||
* @param {!Object} argsObj arguments for the operation.
|
||||
* @param {!Function} callback For XhrIo result throws.
|
||||
* @param {string=} opt_newId name for the new resource.
|
||||
* @private
|
||||
*/
|
||||
registry.Resource.prototype.send_ =
|
||||
function(opCode, argsObj, callback, opt_newId) {
|
||||
// NB: must be declared this way in order to avoid compiler renaming
|
||||
var req = {};
|
||||
req['op'] = opCode;
|
||||
req['args'] = argsObj;
|
||||
if (opt_newId) {
|
||||
this.uri.setPath(this.uri.getPath() + '/' + opt_newId);
|
||||
}
|
||||
this.sendXhrIo(goog.json.serialize(req), callback);
|
||||
};
|
175
java/google/registry/ui/js/resource_component.js
Normal file
|
@ -0,0 +1,175 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
goog.provide('registry.ResourceComponent');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.json');
|
||||
goog.require('registry.EditItem');
|
||||
goog.require('registry.forms');
|
||||
goog.require('registry.util');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The ResourceComponent class respresents server state for a named
|
||||
* resource and binds UI CRUD operations on it, or its constituent
|
||||
* collection.
|
||||
* @param {!registry.Console} console console singleton.
|
||||
* @param {!registry.Resource} resource the RESTful resource.
|
||||
* @param {!Function} itemTmpl
|
||||
* @param {Function} renderSetCb may be null if this resource is only
|
||||
* ever an item.
|
||||
* @constructor
|
||||
* @extends {registry.EditItem}
|
||||
*/
|
||||
registry.ResourceComponent = function(
|
||||
console,
|
||||
resource,
|
||||
itemTmpl,
|
||||
renderSetCb) {
|
||||
registry.ResourceComponent.base(this, 'constructor', console, itemTmpl);
|
||||
|
||||
/** @type {!registry.Resource} */
|
||||
this.resource = resource;
|
||||
|
||||
/** @type {Function} */
|
||||
this.renderSetCb = renderSetCb;
|
||||
};
|
||||
goog.inherits(registry.ResourceComponent, registry.EditItem);
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.ResourceComponent.prototype.bindToDom = function(id) {
|
||||
registry.ResourceComponent.base(this, 'bindToDom', id);
|
||||
this.fetchItem(id);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.ResourceComponent.prototype.back = function() {
|
||||
this.console.view(this.basePath);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.ResourceComponent.prototype.fetchItem = function(id) {
|
||||
this.resource.read({}, goog.bind(this.handleFetchItem, this, id));
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.ResourceComponent.prototype.handleFetchItem = function(id, rsp) {
|
||||
// XXX: Two different protocols are supported here. The new style used in the
|
||||
// registrar console is first, followed by the item/set style used by
|
||||
// the admin console.
|
||||
if ('status' in rsp) {
|
||||
// New style.
|
||||
if (rsp.status == 'SUCCESS') {
|
||||
this.model = rsp.results[0];
|
||||
this.model.readonly = true;
|
||||
this.processItem();
|
||||
this.renderItem(this.model);
|
||||
} else {
|
||||
// XXX: Happens if the server restarts while the client has an
|
||||
// open-session. This should retry.
|
||||
registry.forms.resetErrors();
|
||||
registry.forms.displayError(rsp['message'], rsp['field']);
|
||||
}
|
||||
} else if ('item' in rsp) {
|
||||
this.model = rsp.item;
|
||||
this.model.readonly = true;
|
||||
this.processItem();
|
||||
this.renderItem(this.model);
|
||||
} else if ('set' in rsp && this.renderSetCb != null) {
|
||||
// XXX: This conditional logic should be hoisted to edit_item when
|
||||
// collection support is improved.
|
||||
goog.dom.removeChildren(goog.dom.getRequiredElement('reg-appbar'));
|
||||
this.renderSetCb(goog.dom.getRequiredElement('reg-content'), rsp);
|
||||
} else {
|
||||
registry.util.log('unknown message type in handleFetchItem');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.ResourceComponent.prototype.sendUpdate = function() {
|
||||
var modelCopy = /** @type {!Object}
|
||||
*/ (goog.json.parse(goog.json.serialize(this.model)));
|
||||
this.prepareUpdate(modelCopy);
|
||||
if (this.id) {
|
||||
this.resource.update(modelCopy, goog.bind(this.handleUpdateResponse, this));
|
||||
} else {
|
||||
this.resource.update(modelCopy, goog.bind(this.handleCreateResponse, this));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.ResourceComponent.prototype.prepareUpdate = function(modelCopy) {
|
||||
var form = registry.util.parseForm('item');
|
||||
for (var ndx in form) {
|
||||
modelCopy[ndx] = form[ndx];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
registry.ResourceComponent.prototype.handleUpdateResponse = function(rsp) {
|
||||
if (rsp.status) {
|
||||
if (rsp.status != 'SUCCESS') {
|
||||
registry.forms.resetErrors();
|
||||
registry.forms.displayError(rsp['message'], rsp['field']);
|
||||
return rsp;
|
||||
}
|
||||
// XXX: Vestigial state from admin console. Shouldn't be possible to be
|
||||
// null.
|
||||
if (this.id) {
|
||||
this.fetchItem(this.id || '');
|
||||
this.toggleEdit();
|
||||
}
|
||||
return rsp;
|
||||
}
|
||||
// XXX: Should be removed when admin console uses new response format.
|
||||
if (rsp instanceof Object && 'results' in rsp) {
|
||||
registry.util.butter(rsp['results']);
|
||||
this.bindToDom(this.nextId || '');
|
||||
this.nextId = null;
|
||||
} else {
|
||||
registry.util.butter(rsp.toString());
|
||||
}
|
||||
return rsp;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle resource create response.
|
||||
* @param {!Object} rsp Decoded JSON response from the server.
|
||||
* @return {!Object} a result object describing next steps. On
|
||||
* success, if next is defined, visit(ret.next) is called, otherwise
|
||||
* if err is set, the butterbar message is set to it.
|
||||
*/
|
||||
registry.ResourceComponent.prototype.handleCreateResponse =
|
||||
goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Handle resource delete response.
|
||||
* @param {!Object} rsp Decoded JSON response from the server.
|
||||
* @return {!Object} a result object describing next steps. On
|
||||
* success, if next is defined, visit(ret.next) is called, otherwise
|
||||
* if err is set, the butterbar message is set to it.
|
||||
*/
|
||||
registry.ResourceComponent.prototype.handleDeleteResponse =
|
||||
goog.abstractMethod;
|
125
java/google/registry/ui/js/session.js
Normal file
|
@ -0,0 +1,125 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
goog.provide('registry.Session');
|
||||
|
||||
goog.require('goog.json');
|
||||
goog.require('goog.net.XhrIo');
|
||||
goog.require('goog.structs.Map');
|
||||
goog.require('registry.util');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* XHR launcher for both JSON and XML requests.
|
||||
* @param {!goog.Uri} defaultUri URI to which requests are POSTed.
|
||||
* @param {string} xsrfToken Cross-site request forgery protection token.
|
||||
* @param {!registry.Session.ContentType} contentType Payload mode.
|
||||
* @constructor
|
||||
* @template REQUEST, RESPONSE
|
||||
*/
|
||||
registry.Session = function(defaultUri, xsrfToken, contentType) {
|
||||
|
||||
/**
|
||||
* URI to which requests are posted.
|
||||
* @protected {!goog.Uri}
|
||||
* @const
|
||||
*/
|
||||
this.uri = defaultUri;
|
||||
|
||||
/**
|
||||
* Content type set in request body.
|
||||
* @private {!registry.Session.ContentType}
|
||||
* @const
|
||||
*/
|
||||
this.contentType_ = contentType;
|
||||
|
||||
/**
|
||||
* XHR request headers.
|
||||
* @private {!goog.structs.Map.<string, string>}
|
||||
* @const
|
||||
*/
|
||||
this.headers_ = new goog.structs.Map(
|
||||
'Content-Type', contentType,
|
||||
'X-CSRF-Token', xsrfToken,
|
||||
'X-Requested-With', 'XMLHttpRequest');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Payload modes supported by this class.
|
||||
* @enum {string}
|
||||
*/
|
||||
registry.Session.ContentType = {
|
||||
JSON: 'application/json; charset=utf-8',
|
||||
EPP: 'application/epp+xml'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Abstract method to send a request to the server.
|
||||
* @param {REQUEST} body HTTP request body as a string or JSON object.
|
||||
* @param {function(RESPONSE)} onSuccess XHR success callback.
|
||||
* @param {function(string)=} opt_onError XHR error callback. The default action
|
||||
* is to show a bloody butterbar.
|
||||
* @final
|
||||
*/
|
||||
registry.Session.prototype.sendXhrIo =
|
||||
function(body, onSuccess, opt_onError) {
|
||||
goog.net.XhrIo.send(
|
||||
this.uri.toString(),
|
||||
goog.bind(this.onXhrComplete_, this, onSuccess,
|
||||
opt_onError || goog.bind(this.displayError_, this)),
|
||||
'POST',
|
||||
goog.isObject(body) ? goog.json.serialize(body) : body,
|
||||
this.headers_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler invoked when an asynchronous request is complete.
|
||||
* @param {function(RESPONSE)} onSuccess Success callback.
|
||||
* @param {function(string)} onError Success callback.
|
||||
* @param {{target: !goog.net.XhrIo}} e XHR event.
|
||||
* @private
|
||||
*/
|
||||
registry.Session.prototype.onXhrComplete_ = function(onSuccess, onError, e) {
|
||||
if (e.target.isSuccess()) {
|
||||
onSuccess(/** @type {!RESPONSE} */ (
|
||||
this.contentType_ == registry.Session.ContentType.JSON ?
|
||||
e.target.getResponseJson(registry.Session.PARSER_BREAKER_) :
|
||||
e.target.getResponseXml()));
|
||||
} else {
|
||||
onError(e.target.getLastError());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* JSON response prefix which prevents evaluation.
|
||||
* @private {string}
|
||||
* @const
|
||||
*/
|
||||
registry.Session.PARSER_BREAKER_ = ')]}\'\n';
|
||||
|
||||
|
||||
/**
|
||||
* Displays {@code message} to user in bloody butterbar.
|
||||
* @param {string} message
|
||||
* @private
|
||||
*/
|
||||
registry.Session.prototype.displayError_ = function(message) {
|
||||
registry.util.butter(
|
||||
this.uri.toString() + ': ' + message + '. Please reload.', true);
|
||||
};
|
216
java/google/registry/ui/js/util.js
Normal file
|
@ -0,0 +1,216 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
goog.provide('registry.util');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.classlist');
|
||||
goog.require('goog.soy');
|
||||
|
||||
|
||||
/**
|
||||
* Logging function that delegates to {@code console.log()}.
|
||||
* @param {...*} var_args
|
||||
*/
|
||||
registry.util.log = function(var_args) {
|
||||
if (goog.DEBUG) {
|
||||
if (goog.isDef(goog.global.console) &&
|
||||
goog.isDef(goog.global.console['log'])) {
|
||||
goog.global.console.log.apply(goog.global.console, arguments);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* CSS class for hiding an element whose visibility can be toggled.
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
registry.util.cssHidden = goog.getCssName('hidden');
|
||||
|
||||
|
||||
/**
|
||||
* CSS class for showing an element whose visibility can be toggled.
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
registry.util.cssShown = goog.getCssName('shown');
|
||||
|
||||
|
||||
/**
|
||||
* Changes element visibility by toggling CSS {@code shown} to {@code hidden}.
|
||||
* @param {!Element|string} element Element or id attribute of element.
|
||||
* @param {boolean} visible Shows {@code element} if true, or else hides it.
|
||||
*/
|
||||
registry.util.setVisible = function(element, visible) {
|
||||
goog.dom.classlist.addRemove(
|
||||
goog.dom.getElement(element),
|
||||
visible ? registry.util.cssHidden : registry.util.cssShown,
|
||||
visible ? registry.util.cssShown : registry.util.cssHidden);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Show a buttebar with the given message. A dismiss link will be added.
|
||||
* @param {string} message the message to show the user.
|
||||
* @param {boolean=} opt_isFatal indicates butterbar should be blood red. This
|
||||
* should only be used when an RPC returns a non-200 status.
|
||||
*/
|
||||
registry.util.butter = function(message, opt_isFatal) {
|
||||
goog.dom.setTextContent(
|
||||
goog.dom.getElementByClass(goog.getCssName('kd-butterbar-text')),
|
||||
message);
|
||||
var butterbar =
|
||||
goog.dom.getRequiredElementByClass(goog.getCssName('kd-butterbar'));
|
||||
registry.util.setVisible(butterbar, true);
|
||||
if (opt_isFatal) {
|
||||
goog.dom.classlist.add(butterbar, goog.getCssName('fatal'));
|
||||
} else {
|
||||
goog.dom.classlist.remove(butterbar, goog.getCssName('fatal'));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hides the butterbar.
|
||||
*/
|
||||
registry.util.unbutter = function() {
|
||||
registry.util.setVisible(
|
||||
goog.dom.getRequiredElementByClass(goog.getCssName('kd-butterbar')),
|
||||
false);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Renders the tmpl at and then moves it before the given elt.
|
||||
* @param {Element|string} id dom id of the refElt to render before.
|
||||
* @param {function()} tmpl template to render.
|
||||
* @param {!Object} tmplParams params to pass to the template.
|
||||
* @return {!Element} the rendered row.
|
||||
*/
|
||||
registry.util.renderBeforeRow = function(id, tmpl, tmplParams) {
|
||||
var refElt = goog.dom.getElement(id);
|
||||
goog.soy.renderElement(refElt, tmpl, tmplParams);
|
||||
var prevSib = goog.dom.getPreviousElementSibling(refElt);
|
||||
goog.dom.removeNode(refElt);
|
||||
refElt.removeAttribute('id');
|
||||
goog.dom.insertSiblingAfter(refElt, prevSib);
|
||||
var newAnchorRefElt = goog.dom.createDom(refElt.tagName, {'id': id});
|
||||
goog.dom.insertSiblingAfter(newAnchorRefElt, refElt);
|
||||
return refElt;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Turns an HTML form's named elements into a JSON data structure with
|
||||
* Pablo's black magick.
|
||||
* @param {string} formName the name of the form to be parsed.
|
||||
* @throws {Error} if {@code formName} couldn't be found.
|
||||
* @return {!Object} the parsed form values as an object.
|
||||
*/
|
||||
registry.util.parseForm = function(formName) {
|
||||
var form = /** @type {HTMLFormElement}
|
||||
*/ (document.querySelector('form[name=' + formName + ']'));
|
||||
if (form == null) {
|
||||
throw new Error('No such form named ' + formName);
|
||||
}
|
||||
var obj = {};
|
||||
// Find names first, since e.g. radio buttons have two elts with the
|
||||
// same name.
|
||||
var eltNames = {};
|
||||
for (var i = 0; i < form.elements.length; i++) {
|
||||
var elt = form.elements[i];
|
||||
if (elt.name == '') {
|
||||
continue;
|
||||
}
|
||||
eltNames[elt.name] = null;
|
||||
}
|
||||
for (var eltName in eltNames) {
|
||||
var elt = form.elements[eltName];
|
||||
var val;
|
||||
if (elt.type == 'checkbox') {
|
||||
val = elt.checked;
|
||||
} else {
|
||||
val = elt.value;
|
||||
}
|
||||
registry.util.expandObject_(obj, eltName, val);
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Give the object a value at the given path. Paths are split on dot
|
||||
* and array elements are recognized.
|
||||
*
|
||||
* <ul>
|
||||
* <li>a = 1
|
||||
* <li>foo:a = 1.5
|
||||
* <li>b.c = 2
|
||||
* <li>b.d = 3
|
||||
* <li>c[0] = 4
|
||||
* <li>c[1] = 5
|
||||
* <li>d[0].a = 6
|
||||
* <li>d[1].foo:b = 7
|
||||
* <li>d[1].@b = 8
|
||||
* </ul>
|
||||
*
|
||||
* Yields the following object:
|
||||
* <pre>
|
||||
* {
|
||||
* a: '1',
|
||||
* 'foo:a': '1.5',
|
||||
* b: {
|
||||
* c: '2',
|
||||
* d: '3'
|
||||
* },
|
||||
* c: ['4','5'],
|
||||
* d: [{a:'6'},{'foo:b':'7'},{'@b':'8'}]
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param {!Object} obj
|
||||
* @param {string} pathSpec
|
||||
* @param {string|Array.<string>|null} val
|
||||
* @private
|
||||
*/
|
||||
registry.util.expandObject_ = function(obj, pathSpec, val) {
|
||||
var path = pathSpec.split('.');
|
||||
for (var p = 0; p < path.length; p++) {
|
||||
var fieldName = path[p];
|
||||
var arrElt = fieldName.match(/^([\w:]+)\[(\d+)\]$/);
|
||||
if (arrElt) {
|
||||
var arrName = arrElt[1];
|
||||
var arrNdx = arrElt[2];
|
||||
if (!obj[arrName]) {
|
||||
obj[arrName] = [];
|
||||
}
|
||||
obj = obj[arrName];
|
||||
fieldName = arrNdx;
|
||||
if (!obj[arrNdx]) {
|
||||
obj[arrNdx] = {};
|
||||
}
|
||||
} else {
|
||||
if (!obj[fieldName]) {
|
||||
obj[fieldName] = {};
|
||||
}
|
||||
}
|
||||
if (p == path.length - 1) {
|
||||
obj[fieldName] = val;
|
||||
} else {
|
||||
obj = obj[fieldName];
|
||||
}
|
||||
}
|
||||
};
|
89
java/google/registry/ui/js/xml.js
Normal file
|
@ -0,0 +1,89 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
goog.provide('registry.xml');
|
||||
goog.provide('registry.xml.XmlJson');
|
||||
|
||||
goog.require('goog.dom.NodeType');
|
||||
goog.require('goog.object');
|
||||
|
||||
|
||||
/**
|
||||
* Turns XML document into a JSON data structure. This function is similar to
|
||||
* <a href="https://developer.mozilla.org/en-US/docs/JXON">Mozilla JXON</a>
|
||||
* except it handles text differently. This routine will not coalesce text
|
||||
* interspersed with elements. This routine does not coerce string types to
|
||||
* number, boolean, or null.
|
||||
* @param {!Node} node
|
||||
* @return {!Object<string, registry.xml.XmlJson>}
|
||||
* @throws {Error} upon encountering interspersed text.
|
||||
*/
|
||||
registry.xml.convertToJson = function(node) {
|
||||
var result = goog.object.create();
|
||||
if (goog.isDefAndNotNull(node.attributes)) {
|
||||
for (var i = 0; i < node.attributes.length; i++) {
|
||||
var attr = node.attributes.item(i);
|
||||
goog.object.set(result, '@' + attr.name, attr.value || '');
|
||||
}
|
||||
}
|
||||
for (var j = 0; j < node.childNodes.length; j++) {
|
||||
var child = node.childNodes.item(j);
|
||||
switch (child.nodeType) {
|
||||
case goog.dom.NodeType.TEXT:
|
||||
case goog.dom.NodeType.CDATA_SECTION:
|
||||
var text = String(child.nodeValue).trim();
|
||||
if (text != '') {
|
||||
var curr = goog.object.get(result, registry.xml.jsonValueFieldName_);
|
||||
if (goog.isDef(curr)) {
|
||||
throw new Error(
|
||||
'XML text "' + curr + '" interspersed with "' + text + '"');
|
||||
}
|
||||
goog.object.set(result, registry.xml.jsonValueFieldName_, text);
|
||||
}
|
||||
break;
|
||||
case goog.dom.NodeType.ELEMENT:
|
||||
var json = registry.xml.convertToJson(child);
|
||||
var name = child.nodeName;
|
||||
if (goog.object.containsKey(result, name)) {
|
||||
var field = goog.object.get(result, name);
|
||||
if (goog.isArray(field)) {
|
||||
field.push(json);
|
||||
} else {
|
||||
goog.object.set(result, name, [field, json]);
|
||||
}
|
||||
} else {
|
||||
goog.object.set(result, name, json);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* XML JSON object recursive type definition.
|
||||
* @typedef {(string|
|
||||
* !Array<registry.xml.XmlJson>|
|
||||
* !Object<string, registry.xml.XmlJson>)}
|
||||
*/
|
||||
registry.xml.XmlJson;
|
||||
|
||||
|
||||
/**
|
||||
* XML JSON value field name, inherited from JXON.
|
||||
* @private {string}
|
||||
* @const
|
||||
*/
|
||||
registry.xml.jsonValueFieldName_ = 'keyValue';
|
16
java/google/registry/ui/package-info.java
Normal file
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
@javax.annotation.ParametersAreNonnullByDefault
|
||||
package com.google.domain.registry.ui;
|