From 50f656e7410c9af15fc1a74ae4f11e11079222d5 Mon Sep 17 00:00:00 2001 From: Nikolai Novkirishki Date: Fri, 30 Nov 2018 16:38:07 +0200 Subject: [PATCH 01/32] Revert "Fix for the theme test (#67)" This reverts commit ae271da31e7f5fe8282e6ee57725b46f0b884645. --- tests/e2e/app-elements/item-list.component.ts | 9 --------- tests/e2e/app-elements/item-list.map.ts | 5 ----- tests/e2e/app-elements/theme.component.ts | 8 ++++++++ tests/e2e/app-specs/verify-extensions.e2e-spec.ts | 4 +--- 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/tests/e2e/app-elements/item-list.component.ts b/tests/e2e/app-elements/item-list.component.ts index c639e878..15936495 100644 --- a/tests/e2e/app-elements/item-list.component.ts +++ b/tests/e2e/app-elements/item-list.component.ts @@ -5,9 +5,6 @@ import { ItemListMap } from "./item-list.map"; import { BrowserWaitForElement, BrowserGetUrl } from "../helpers/browser-helpers"; import { CONTENT_PAGE_URL, DEFAULT_ITEMS_NUMBER } from "../helpers/constants"; -const backgroundColorAttribute = "backgroundColor"; -const expectedButtonColor = "rgba(0, 108, 217, 1)"; - export class ItemList { static async VerifyBasicUIElements(typeName: string, itemTitlePlural: string) { @@ -51,12 +48,6 @@ export class ItemList { await item.click(); } - static async VerifyThemeButtonColor() { - await BrowserWaitForElement(ItemListMap.GetCreateItemButton()); - const actualButtonColor = await ItemListMap.GetCreateItemButton().getCssValue(backgroundColorAttribute); - expect(actualButtonColor).toBe(expectedButtonColor); - } - private static async VerifyHeaders(headers: string[]) { const headersNumber = headers.length; try { diff --git a/tests/e2e/app-elements/item-list.map.ts b/tests/e2e/app-elements/item-list.map.ts index 2b8577ba..cbc946f9 100644 --- a/tests/e2e/app-elements/item-list.map.ts +++ b/tests/e2e/app-elements/item-list.map.ts @@ -18,11 +18,6 @@ export class ItemListMap { return itemRow.element(by.css("[title=Actions]")); } - public static GetCreateItemButton(): ElementFinder { - const listHeaderBar = element(by.css(".sf-main__header")); - return listHeaderBar.element(by.cssContainingText("button.sf-button.-action", "Create a")); - } - private static GetTableRow(rowTitle: string): ElementFinder { return element(by.cssContainingText(".tree-node-level-1", rowTitle)); } diff --git a/tests/e2e/app-elements/theme.component.ts b/tests/e2e/app-elements/theme.component.ts index b50b9a92..91216bf9 100644 --- a/tests/e2e/app-elements/theme.component.ts +++ b/tests/e2e/app-elements/theme.component.ts @@ -5,6 +5,8 @@ import { BrowserWaitForElement } from "../helpers/browser-helpers"; import { EC } from "../helpers/constants"; import { browser } from "protractor"; +const backgroundColorAttribute = "backgroundColor"; +const expectedButtonColor = "rgba(0, 99, 208, 1)"; const timeToWait = 3000; export class Theme { @@ -19,4 +21,10 @@ export class Theme { await ThemeMap.UseSelectedButton.click(); await browser.wait(EC.elementToBeClickable(ThemeMap.UseSelectedButton), timeToWait); } + + public static async ValidateButtonColor() { + const button = await ThemeMap.UseSelectedButton; + const actualButtonColor = await button.getCssValue(backgroundColorAttribute); + expect(actualButtonColor).toBe(expectedButtonColor); + } } diff --git a/tests/e2e/app-specs/verify-extensions.e2e-spec.ts b/tests/e2e/app-specs/verify-extensions.e2e-spec.ts index ad2bdb85..76db6bb7 100644 --- a/tests/e2e/app-specs/verify-extensions.e2e-spec.ts +++ b/tests/e2e/app-specs/verify-extensions.e2e-spec.ts @@ -62,8 +62,6 @@ describe("Verify extensions", () => { await BrowserNavigate(THEME_URL); await Theme.SelectTheme("Sample"); await Theme.UseSelectedTheme(); - - await BrowserNavigate(CONTENT_NEWS_URL); - await ItemList.VerifyThemeButtonColor(); + await Theme.ValidateButtonColor(); }); }); From 777efeb30b339e3d22f590c9692d5566ad51c801 Mon Sep 17 00:00:00 2001 From: Nikolai Novkirishki Date: Fri, 30 Nov 2018 16:38:10 +0200 Subject: [PATCH 02/32] Revert "Ability to customize new UI color schema (#62)" This reverts commit c594b01b9b7204a932ea8dee08e2ac338ca56e8e. --- README.md | 6 +- __extensions_index.ts | 4 +- tests/e2e/app-elements/theme.component.ts | 30 ------- tests/e2e/app-elements/theme.map.ts | 9 --- .../app-specs/verify-extensions.e2e-spec.ts | 12 +-- tests/e2e/helpers/constants.ts | 1 - theme/README.md | 57 ------------- theme/index.ts | 14 ---- theme/theme-provider.ts | 81 ------------------- 9 files changed, 4 insertions(+), 210 deletions(-) delete mode 100644 tests/e2e/app-elements/theme.component.ts delete mode 100644 tests/e2e/app-elements/theme.map.ts delete mode 100644 theme/README.md delete mode 100644 theme/index.ts delete mode 100644 theme/theme-provider.ts diff --git a/README.md b/README.md index f3f39efd..f2ca1e2b 100644 --- a/README.md +++ b/README.md @@ -213,10 +213,6 @@ Holds the type of component to be instantiated and what properties are assigned Contains commands that serve as buttons in the dialog that is displayed. -### Admin App custom theme - -You can customize the appearance of the Admin App by modifying specific components of the user interface. For example, you can customize buttons’ color, background, and text, as well as other supplementary text on the UI. For more details, see [Admin App custom theme](./theme/README.md#custom-theme-for-sitefinity-cms-admin-app) - ### Access data from OData services -You can make HTTP calls to Sitefinity CMS OData services via the Angular **HttpClient**. When you make the request, you use the HTTP_PREFIX constant, so that the Admin App automatically detects this is a request to Sitefinity CMS and completes the request accordingly. +You can HTTP calls to Sitefinity CMS OData services via the Angular **HttpClient**. When you make the request, you use the HTTP_PREFIX constant, so that the Admin App automatically detects this is a request to Sitefinity CMS and completes the request accordingly. diff --git a/__extensions_index.ts b/__extensions_index.ts index 6fee42d7..77822616 100644 --- a/__extensions_index.ts +++ b/__extensions_index.ts @@ -4,7 +4,6 @@ import { GridExtenderModule } from "./grid-extender"; import { CustomFieldsModule } from "./custom-fields"; import { CommandsExtenderModule } from "./commands-extender"; import { EditorExtenderModule } from "./editor-extender"; -import { ThemeModule } from "./theme"; /** * The entry point of the extensions. Each extension bundle needs to have exactly one export @@ -20,8 +19,7 @@ export class SamplesExtension implements Extension { GridExtenderModule, CustomFieldsModule, CommandsExtenderModule, - EditorExtenderModule, - ThemeModule + EditorExtenderModule ]; } } diff --git a/tests/e2e/app-elements/theme.component.ts b/tests/e2e/app-elements/theme.component.ts deleted file mode 100644 index 91216bf9..00000000 --- a/tests/e2e/app-elements/theme.component.ts +++ /dev/null @@ -1,30 +0,0 @@ -require("jasmine-expect"); - -import { ThemeMap } from "./theme.map"; -import { BrowserWaitForElement } from "../helpers/browser-helpers"; -import { EC } from "../helpers/constants"; -import { browser } from "protractor"; - -const backgroundColorAttribute = "backgroundColor"; -const expectedButtonColor = "rgba(0, 99, 208, 1)"; -const timeToWait = 3000; - -export class Theme { - public static async SelectTheme(themeName: string) { - await BrowserWaitForElement(ThemeMap.UseSelectedButton); - const item = ThemeMap.GetThemeRow(themeName); - await item.click(); - } - - public static async UseSelectedTheme() { - await BrowserWaitForElement(ThemeMap.UseSelectedButton); - await ThemeMap.UseSelectedButton.click(); - await browser.wait(EC.elementToBeClickable(ThemeMap.UseSelectedButton), timeToWait); - } - - public static async ValidateButtonColor() { - const button = await ThemeMap.UseSelectedButton; - const actualButtonColor = await button.getCssValue(backgroundColorAttribute); - expect(actualButtonColor).toBe(expectedButtonColor); - } -} diff --git a/tests/e2e/app-elements/theme.map.ts b/tests/e2e/app-elements/theme.map.ts deleted file mode 100644 index 7c2bf245..00000000 --- a/tests/e2e/app-elements/theme.map.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { element, by, ElementFinder } from "protractor"; - -export class ThemeMap { - public static UseSelectedButton: ElementFinder = element(by.cssContainingText(".sf-button", "Use selected")); - - public static GetThemeRow(themeName: string): ElementFinder { - return element(by.cssContainingText(".tree-node-level-1", themeName)); - } -} diff --git a/tests/e2e/app-specs/verify-extensions.e2e-spec.ts b/tests/e2e/app-specs/verify-extensions.e2e-spec.ts index 76db6bb7..44c13320 100644 --- a/tests/e2e/app-specs/verify-extensions.e2e-spec.ts +++ b/tests/e2e/app-specs/verify-extensions.e2e-spec.ts @@ -1,12 +1,11 @@ import { initAuth } from "../helpers/authentication-manager"; import { ItemList } from "../app-elements/item-list.component"; -import { USERNAME, PASSWORD, TIMEOUT, DYNAMIC_ITEM_HEADERS, TABLE_HEADERS_CONSTANTS, CONTENT_NEWS_URL, NEWS_TYPE_NAME, SAMPLE_TEXT_CONTENT, THEME_URL } from "../helpers/constants"; +import { USERNAME, PASSWORD, TIMEOUT, DYNAMIC_ITEM_HEADERS, TABLE_HEADERS_CONSTANTS, CONTENT_NEWS_URL, NEWS_TYPE_NAME, SAMPLE_TEXT_CONTENT } from "../helpers/constants"; import { BrowserNavigate, BrowserWaitForElement, SelectAllAndPasteText } from "../helpers/browser-helpers"; import { PrintPreview } from "../app-elements/print-preview.component"; import { ItemDetails } from "../app-elements/item-details.component"; import { VideosModal } from "../app-elements/videos-modal.component"; import { ItemListMap } from "../app-elements/item-list.map"; -import { Theme } from "../app-elements/theme.component"; describe("Verify extensions", () => { const typeToTest = "News"; @@ -43,7 +42,7 @@ describe("Verify extensions", () => { await ItemDetails.VerifyHtmlToolbarWordCount(SAMPLE_TEXT_CONTENT); }); - it("videos toolbar button ", async () => { + it("word count editor toolbar button ", async () => { await ItemDetails.ClickHtmlToolbarSitefinityVideos(); await VideosModal.VerifyModalTitle(); await VideosModal.CancelModal(); @@ -57,11 +56,4 @@ describe("Verify extensions", () => { await ItemDetails.ExpandHtmlField(); await ItemDetails.VerifyAndClickSymbolListButton(); }); - - it("applied theme", async () => { - await BrowserNavigate(THEME_URL); - await Theme.SelectTheme("Sample"); - await Theme.UseSelectedTheme(); - await Theme.ValidateButtonColor(); - }); }); diff --git a/tests/e2e/helpers/constants.ts b/tests/e2e/helpers/constants.ts index 9c55b44b..0127bb25 100644 --- a/tests/e2e/helpers/constants.ts +++ b/tests/e2e/helpers/constants.ts @@ -10,7 +10,6 @@ export const CONFIG_PAGE_URL = `${BASE_URL}config`; export const CONTENT_PAGE_URL = `${BASE_URL}content/`; export const CONTENT_BLOGS_PAGE_URL = `${BASE_URL}content/blogs`; export const CONTENT_NEWS_URL = `${BASE_URL}content/newsitems`; -export const THEME_URL = `${BASE_URL}theme`; export const EC = protractor.ExpectedConditions; export const URL_IN_CONFIG_FILE = browser.params.sfUrl; diff --git a/theme/README.md b/theme/README.md deleted file mode 100644 index 802d6bdc..00000000 --- a/theme/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# Admin App custom theme - -You can customize the appearance of the Admin App by modifying specific components of the user interface. Thus, you create a custom theme for the new content experience backend, that is, the Admin App. You can customize the following components: - -* Button colors, such as border, background, and text color - -* Secondary text, such as notes and descriptions of UI elements - -* Input placeholder text, such as hints for input fields - -You can also set an outline style for all UI focusable elements, for example, buttons or input fields, and so on. - -To customize the Admin App theme, you need to create a custom implementation of the **ThemeProvider** interface and its **getThemes()** method. The method returns an **Array** of the **ThemeItem** object. Theme items must contain the **name** of your custom theme and an array of the **ThemeVariablesKeyValuePair**. The key-value pair contains the following: - -* **key: ThemeVariables** - The **ThemeVariables** value is an Enum of all exposed CSS variables that can be modified, such as the properties of colors or buttons. - -* **value: string** - The string value is the value that you set for the specific CSS variable, for example, *"#38AB63"*. - -**NOTE:** In key-value pairs, you abide by the following syntax: **{ key: ThemeVariables.DefaultButtonColor, value: "#333" }**. You thus set the color of the default button to be color **#333**. - -| Name | Key | Value | -| --------------------------------------------- |:---------------------------------------------------------:| ---------------------:| -| Default button border color | ThemeVariables.DefaultButtonBorderColor | "#E4E4E4" | -| Default button background color | ThemeVariables.DefaultButtonBackgroundColor | "#FFF" | -| Default button color | ThemeVariables.DefaultButtonColor | "#333" | -| Default button interaction border color | ThemeVariables.DefaultButtonInteractionBorderColor | "#E4E4E4" | -| Default button interaction background color | ThemeVariables.DefaultButtonInteractionBackgroundColor | "#E4E4E4" | -| Default button interaction color | ThemeVariables.DefaultButtonInteractionColor | "#333" | -| Default button disabled border color | ThemeVariables.DefaultButtonDisabledBorderColor | "#EEE" | -| Default button disabled background color | ThemeVariables.DefaultButtonDisabledBackgroundColor | "#FFF" | -| Default button disabled color | ThemeVariables.DefaultButtonDisabledColor | "#E4E4E4" | -| Action button border color | ThemeVariables.ActionButtonBorderColor | "#38AB63" | -| Action button background color | ThemeVariables.ActionButtonBackgroundColor | "#38AB63" | -| Action button color | ThemeVariables.ActionButtonColor | "#FFF" | -| Action button interaction border color | ThemeVariables.ActionButtonInteractionBorderColor | "#1F924A" | -| Action button interaction background color | ThemeVariables.ActionButtonInteractionBackgroundColor | "#1F924A" | -| Action button interaction color | ThemeVariables.ActionButtonInteractionColor | "#FFF" | -| Action button disabled border color: | ThemeVariables.ActionButtonDisabledBorderColor | "#C4E6D1" | -| Action button disabled background color | ThemeVariables.ActionButtonDisabledBackgroundColor | "#C4E6D1" | -| Action button disabled color | ThemeVariables.ActionButtonDisabledColor | "#FFF" | -| Delete button border color | ThemeVariables.DeleteButtonBorderColor | "#FF4848" | -| Delete button background color | ThemeVariables.DeleteButtonBackgroundColor | "#FF4848" | -| Delete button color | ThemeVariables.DeleteButtonColor | "#FFF" | -| Delete button interaction border color | ThemeVariables.DeleteButtonInteractionBorderColor | "#E62F2F" | -| Delete button interaction background color | ThemeVariables.DeleteButtonInteractionBackgroundColor | "#E62F2F" | -| Delete button interaction color | ThemeVariables.DeleteButtonInteractionColor | "#FFF" | -| Delete button disabled border color | ThemeVariables.DeleteButtonDisabledBorderColor | "#FFC8C8" | -| Delete button disabled background color | ThemeVariables.DeleteButtonDisabledBackgroundColor | "#FFC8C8" | -| Delete button disabled color | ThemeVariables.DeleteButtonDisabledColor | "#FFF" | -| Global outline for focusable elements | ThemeVariables.GlobalOutline | "5px solid #DCECF5" | -| Global outline offset for focusable elements | ThemeVariables.GlobalOutlineOffset | "5px" | -| Secondary (note, describtion) text color | ThemeVariables.SecondaryTextColor | "#777" | -| Input placeholders text color | ThemeVariables.InputPlaceholderColor | "#BBB" | - -For a more detailed demonstration of how to modify the Admin App UI elements and how to create a custom theme, check out the **theme-provider.ts** file. diff --git a/theme/index.ts b/theme/index.ts deleted file mode 100644 index dd60abe5..00000000 --- a/theme/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { NgModule } from "@angular/core"; -import { CommonModule } from "@angular/common"; -import { SAMPLE_THEME_PROVIDER } from "./theme-provider"; - -@NgModule({ - providers: [ - SAMPLE_THEME_PROVIDER - ], - imports: [ - CommonModule - ] -}) - -export class ThemeModule { } diff --git a/theme/theme-provider.ts b/theme/theme-provider.ts deleted file mode 100644 index 0e87e28b..00000000 --- a/theme/theme-provider.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Injectable, ClassProvider } from "@angular/core"; -import { ThemeProvider, THEME_TOKEN } from "progress-sitefinity-adminapp-sdk/app/api/v1/theme/theme-provider"; -import { ThemeItem } from "progress-sitefinity-adminapp-sdk/app/api/v1/theme/theme-item"; -import { ThemeVariables } from "progress-sitefinity-adminapp-sdk/app/api/v1/theme/theme-variables"; -import { ThemeVariablesKeyValuePair } from "progress-sitefinity-adminapp-sdk/app/api/v1/theme/theme-variable-key-value-pair"; - -@Injectable() -class SampleThemeProvider implements ThemeProvider { - // #region Sample theme variables - private sampleThemeVariables: Array = [{ - key: ThemeVariables.DefaultButtonBorderColor, - value: "#333" - }, { - key: ThemeVariables.DefaultButtonBackgroundColor, - value: "#FFF" - }, { - key: ThemeVariables.DefaultButtonColor, - value: "#000" - }, { - key: ThemeVariables.DefaultButtonInteractionBorderColor, - value: "#E4E4E4" - }, { - key: ThemeVariables.DefaultButtonInteractionBackgroundColor, - value: "#E4E4E4" - }, { - key: ThemeVariables.DefaultButtonInteractionColor, - value: "#333" - }, { - key: ThemeVariables.DefaultButtonDisabledBorderColor, - value: "#C2C2C2" - }, { - key: ThemeVariables.DeleteButtonDisabledBackgroundColor, - value: "#FFF" - }, { - key: ThemeVariables.DefaultButtonDisabledColor, - value: "#C2C2C2" - }, { - key: ThemeVariables.ActionButtonBorderColor, - value: "#006CD9" - }, { - key: ThemeVariables.ActionButtonBackgroundColor, - value: "#006CD9" - }, { - key: ThemeVariables.ActionButtonColor, - value: "#FFF" - }, { - key: ThemeVariables.ActionButtonInteractionBorderColor, - value: "#0053C0" - }, { - key: ThemeVariables.ActionButtonInteractionBackgroundColor, - value: "#0053C0" - }, { - key: ThemeVariables.ActionButtonInteractionColor, - value: "#FFF" - }, { - key: ThemeVariables.ActionButtonDisabledBorderColor, - value: "#BFD4F3" - }, { - key: ThemeVariables.ActionButtonDisabledBackgroundColor, - value: "#BFD4F3" - }, { - key: ThemeVariables.ActionButtonDisabledColor, - value: "#FFF" - }]; - // #endregion - - private themes: Array = [{ - name: "Sample", - themeVariables: this.sampleThemeVariables - }]; - - getThemes(): Array { - return this.themes; - } -} - -export const SAMPLE_THEME_PROVIDER: ClassProvider = { - multi: true, - provide: THEME_TOKEN, - useClass: SampleThemeProvider -}; From 105bbe5863933a35e86c3d1f1c269fbeaad6d989 Mon Sep 17 00:00:00 2001 From: Decho Dechev Date: Mon, 3 Dec 2018 14:11:01 +0200 Subject: [PATCH 03/32] Reverted changes to css locators. (#70) These are not yet checked in to Iris fixes --- tests/e2e/app-elements/item-list.map.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/app-elements/item-list.map.ts b/tests/e2e/app-elements/item-list.map.ts index cbc946f9..5f3c3fd5 100644 --- a/tests/e2e/app-elements/item-list.map.ts +++ b/tests/e2e/app-elements/item-list.map.ts @@ -3,10 +3,10 @@ import { element, by, ElementFinder, ElementArrayFinder } from "protractor"; export class ItemListMap { public static TitleTag: ElementFinder = element.all(by.css(".sf-row h1")).last(); public static PrintPreviewButton: ElementFinder = element(by.cssContainingText("div[role=option]", "Print preview")); - public static CountLabel: ElementFinder = element(by.css("div.sf-row__col.-sf-txt-align-right.-sf-txt-hint")); + public static CountLabel: ElementFinder = element(by.css("div.row__col.-txt-align-right.-txt-hint")); public static TableHeaders: ElementArrayFinder = element(by.className("sf-tree-list__row -head")).all(by.className("sf-tree-list__cell -head")); public static TableElements: ElementArrayFinder = element.all(by.css(".sf-tree-list__row:not(.-head):not(.-loading)")); - public static BackButton: ElementFinder = element(by.css(".sf-button.-toggle.-icon[title='All items']")); + public static BackButton: ElementFinder = element(by.css(".sf-button.-toggle.-icon[title=Back]")); public static GetRowTitleCell(rowTitle: string): ElementFinder { const itemRow = ItemListMap.GetTableRow(rowTitle); From 68b4dee1381d8031aca90b7edbba90b6a86b0435 Mon Sep 17 00:00:00 2001 From: Nikolai Novkirishki Date: Mon, 3 Dec 2018 16:17:42 +0200 Subject: [PATCH 04/32] Item onLoad hook (#63) * Implementation * added test * Fix for the E2E tests --- __extensions_index.ts | 4 +++- item-extender/index.ts | 12 +++++++++++ item-extender/item-hooks-provider.ts | 19 +++++++++++++++++ tests/e2e/app-elements/item-details.map.ts | 1 + .../app-specs/verify-extensions.e2e-spec.ts | 10 ++++++++- tests/e2e/helpers/browser-helpers.ts | 7 +++++++ tests/e2e/protractor.conf.js | 21 +++++++++++++------ 7 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 item-extender/index.ts create mode 100644 item-extender/item-hooks-provider.ts diff --git a/__extensions_index.ts b/__extensions_index.ts index 77822616..2bdac528 100644 --- a/__extensions_index.ts +++ b/__extensions_index.ts @@ -4,6 +4,7 @@ import { GridExtenderModule } from "./grid-extender"; import { CustomFieldsModule } from "./custom-fields"; import { CommandsExtenderModule } from "./commands-extender"; import { EditorExtenderModule } from "./editor-extender"; +import { ItemExtenderModule } from "./item-extender"; /** * The entry point of the extensions. Each extension bundle needs to have exactly one export @@ -19,7 +20,8 @@ export class SamplesExtension implements Extension { GridExtenderModule, CustomFieldsModule, CommandsExtenderModule, - EditorExtenderModule + EditorExtenderModule, + ItemExtenderModule ]; } } diff --git a/item-extender/index.ts b/item-extender/index.ts new file mode 100644 index 00000000..c951ef53 --- /dev/null +++ b/item-extender/index.ts @@ -0,0 +1,12 @@ +import { NgModule } from "@angular/core"; +import { ITEM_HOOKS_PROVIDER } from "./item-hooks-provider"; + +/** + * The grid extender module. + */ +@NgModule({ + providers: [ + ITEM_HOOKS_PROVIDER + ] +}) +export class ItemExtenderModule { /* empty */ } diff --git a/item-extender/item-hooks-provider.ts b/item-extender/item-hooks-provider.ts new file mode 100644 index 00000000..95aa32cd --- /dev/null +++ b/item-extender/item-hooks-provider.ts @@ -0,0 +1,19 @@ +import { Injectable, ClassProvider } from "@angular/core"; +import { ItemHooksProvider, ITEM_HOOKS_PROVIDER_TOKEN, DataItem } from "progress-sitefinity-adminapp-sdk/app/api/v1"; + +@Injectable() +class CustomItemHooksProvider implements ItemHooksProvider { + onItemLoaded(item: DataItem): void { + console.log(`Item is loaded: ${item.data.Title}`); + } +} + +/** + * Export a 'multi' class provider so that multiple instances of the same provider can coexist. + * This allows for more than one provider to be registered within one or more bundles. + */ +export const ITEM_HOOKS_PROVIDER: ClassProvider = { + multi: true, + provide: ITEM_HOOKS_PROVIDER_TOKEN, + useClass: CustomItemHooksProvider +}; diff --git a/tests/e2e/app-elements/item-details.map.ts b/tests/e2e/app-elements/item-details.map.ts index 9fe049ca..65416ead 100644 --- a/tests/e2e/app-elements/item-details.map.ts +++ b/tests/e2e/app-elements/item-details.map.ts @@ -8,6 +8,7 @@ export class ItemDetailsMap { public static ExtendedTitleField: ElementFinder = element(by.css(itemTitleExtensionCssClass)); public static HtmlFieldExpander: ElementFinder = element(by.css(".sf-expand-button")); public static EditorInternalField: ElementFinder = element(by.css(".k-editor.k-editor-inline")); + public static PublishButton: ElementFinder = element(by.cssContainingText("button", "Publish")); public static ToolbarButton(customClass: string): ElementFinder { return element(by.className(customClass)); diff --git a/tests/e2e/app-specs/verify-extensions.e2e-spec.ts b/tests/e2e/app-specs/verify-extensions.e2e-spec.ts index 44c13320..b6b98a57 100644 --- a/tests/e2e/app-specs/verify-extensions.e2e-spec.ts +++ b/tests/e2e/app-specs/verify-extensions.e2e-spec.ts @@ -1,11 +1,12 @@ import { initAuth } from "../helpers/authentication-manager"; import { ItemList } from "../app-elements/item-list.component"; import { USERNAME, PASSWORD, TIMEOUT, DYNAMIC_ITEM_HEADERS, TABLE_HEADERS_CONSTANTS, CONTENT_NEWS_URL, NEWS_TYPE_NAME, SAMPLE_TEXT_CONTENT } from "../helpers/constants"; -import { BrowserNavigate, BrowserWaitForElement, SelectAllAndPasteText } from "../helpers/browser-helpers"; +import { BrowserNavigate, BrowserWaitForElement, SelectAllAndPasteText, BrowserVerifyConsoleOutput } from "../helpers/browser-helpers"; import { PrintPreview } from "../app-elements/print-preview.component"; import { ItemDetails } from "../app-elements/item-details.component"; import { VideosModal } from "../app-elements/videos-modal.component"; import { ItemListMap } from "../app-elements/item-list.map"; +import { ItemDetailsMap } from "../app-elements/item-details.map"; describe("Verify extensions", () => { const typeToTest = "News"; @@ -56,4 +57,11 @@ describe("Verify extensions", () => { await ItemDetails.ExpandHtmlField(); await ItemDetails.VerifyAndClickSymbolListButton(); }); + + it("item hooks", async () => { + await BrowserNavigate(CONTENT_NEWS_URL); + await ItemList.ClickOnItem(itemToVerify); + await BrowserWaitForElement(ItemDetailsMap.PublishButton); + await BrowserVerifyConsoleOutput(itemToVerify); + }); }); diff --git a/tests/e2e/helpers/browser-helpers.ts b/tests/e2e/helpers/browser-helpers.ts index 99bcc474..b7d840e7 100644 --- a/tests/e2e/helpers/browser-helpers.ts +++ b/tests/e2e/helpers/browser-helpers.ts @@ -17,6 +17,13 @@ export async function BrowserNavigate(url: string): Promise { return await browser.get(url); } +export async function BrowserVerifyConsoleOutput(expectedOutput: string) { + return await browser.manage().logs().get('browser').then((browserLog) => { + const log = browserLog.find(entry => entry.level.name === 'INFO' && entry.message.includes(expectedOutput)); + expect(log).not.toBeUndefined(); + }); +} + export async function BrowserVerifyAlert(expectedAlertText: string): Promise { await browser.wait(EC.alertIsPresent(), 5000, "Alert is not shown"); const alertDialog = browser.switchTo().alert(); diff --git a/tests/e2e/protractor.conf.js b/tests/e2e/protractor.conf.js index 108c695d..8f9c2b54 100644 --- a/tests/e2e/protractor.conf.js +++ b/tests/e2e/protractor.conf.js @@ -3,7 +3,7 @@ var jasmineReporters = require("jasmine-reporters"); var HtmlReporter = require('protractor-angular-screenshot-reporter'); var screenshotReporter = new HtmlReporter({ - baseDirectory: './test-results/html-report' + baseDirectory: './test-results/html-report' }).getJasmine2Reporter(); // Override "source-map-support"'s install function, since it is causing issues protractor bootstrap @@ -18,7 +18,7 @@ var junitReporter = new jasmineReporters.JUnitXmlReporter({ var browserParameter; var headlessParameter = false; - + for (let index = 0; index < process.argv.length; index++) { const element = process.argv[index]; @@ -36,11 +36,14 @@ for (let index = 0; index < process.argv.length; index++) { var browserSettings; switch(browserParameter) { - case "chrome": + case "chrome": browserSettings = { browserName: "chrome", chromeOptions: { args: [] + }, + loggingPrefs: { + browser: "ALL" } }; @@ -52,7 +55,7 @@ switch(browserParameter) { ]); } break; - + case "firefox": browserSettings = { browserName: "firefox", @@ -61,6 +64,9 @@ switch(browserParameter) { log: { "level": "error" } + }, + loggingPrefs: { + browser: "ALL" } }; @@ -68,7 +74,7 @@ switch(browserParameter) { browserSettings["moz:firefoxOptions"].args = browserSettings["moz:firefoxOptions"].args.concat([ "--headless" ]); - } + } break; default: @@ -77,6 +83,9 @@ switch(browserParameter) { browserName: "chrome", chromeOptions: { args: [] + }, + loggingPrefs: { + browser: "ALL" } }; } @@ -106,7 +115,7 @@ exports.config = { useAllAngular2AppRoots: true, baseUrl: "http://localhost:3000/", - + onPrepare: function() { browser .manage() From 98b2a5f45d18e01f2b4b8fde1f2702fc3a15610c Mon Sep 17 00:00:00 2001 From: Decho Dechev Date: Fri, 7 Dec 2018 13:15:59 +0200 Subject: [PATCH 05/32] Updated the progress-sitefinity-adminapp-sdk to 1.0.1 (#73) --- package-lock.json | 19 ++++++++++++------- package.json | 2 +- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8e69f2a5..15766042 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2230,7 +2230,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -2645,7 +2646,8 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -2701,6 +2703,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2744,12 +2747,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -4724,9 +4729,9 @@ "dev": true }, "progress-sitefinity-adminapp-sdk": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/progress-sitefinity-adminapp-sdk/-/progress-sitefinity-adminapp-sdk-1.0.0.tgz", - "integrity": "sha512-2HtuXCyVyIX4JRm7WLsWcchbX8JH3TRiW878DjSHcfmJbO8c5LLBoxFrpIr89vvJHuIPnH/9mKFNozpQiTFXFQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/progress-sitefinity-adminapp-sdk/-/progress-sitefinity-adminapp-sdk-1.0.1.tgz", + "integrity": "sha512-fO07Ks5gKDT/rx966o090F+RM1aQDkUUnlicZZcG1Z1Lto7OkIHSX0qIpNvsHyytp5dM7ORK2vr3A+nqs7R04g==", "dev": true }, "promise-inflight": { diff --git a/package.json b/package.json index eeecc366..ad4c411b 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "css-to-string-loader": "0.1.3", "html-loader": "0.4.5", "json-loader": "0.5.4", - "progress-sitefinity-adminapp-sdk": "1.0.0", + "progress-sitefinity-adminapp-sdk": "1.0.1", "rxjs": "6.2.2", "style-loader": "0.20.3", "tslib": "1.7.1", From f4a378bbddd8f3b33cb544e8024680c57e8f990d Mon Sep 17 00:00:00 2001 From: Decho Dechev Date: Tue, 11 Dec 2018 13:27:04 +0200 Subject: [PATCH 06/32] Refactoring extensions E2E tests (#74) * Unstashing changes from experimental branch * Refactoring all maps and wrappers --- tests/e2e/app-elements/editor-popup.map.ts | 2 +- .../app-elements/item-details.component.ts | 37 ++++++-------- tests/e2e/app-elements/item-details.map.ts | 15 ++---- tests/e2e/app-elements/item-list.component.ts | 50 ++----------------- tests/e2e/app-elements/item-list.map.ts | 22 +++----- .../app-specs/verify-extensions.e2e-spec.ts | 31 +++++------- 6 files changed, 46 insertions(+), 111 deletions(-) diff --git a/tests/e2e/app-elements/editor-popup.map.ts b/tests/e2e/app-elements/editor-popup.map.ts index 9fee6a31..1e1ec171 100644 --- a/tests/e2e/app-elements/editor-popup.map.ts +++ b/tests/e2e/app-elements/editor-popup.map.ts @@ -1,6 +1,6 @@ import { element, by, ElementFinder } from "protractor"; export class EditorPopupMap { - public static ToolPopup: ElementFinder = element(by.css(".k-ct-popup.symbol-popup.k-popup")); + public static ToolPopup: ElementFinder = element(by.css(".k-ct-popup.symbol-popup")); public static SymbolCell: ElementFinder = element(by.css(".symbol-cell")); } diff --git a/tests/e2e/app-elements/item-details.component.ts b/tests/e2e/app-elements/item-details.component.ts index 8bcd3e2a..0cee6c2e 100644 --- a/tests/e2e/app-elements/item-details.component.ts +++ b/tests/e2e/app-elements/item-details.component.ts @@ -7,29 +7,18 @@ import { ItemListMap } from "./item-list.map"; export class ItemDetails { static async VerifyHtmlToolbarWordCount(expectedContent: string): Promise { - const wordCountButtonClass = "k-i-Words-count"; - await BrowserWaitForElement(ItemDetailsMap.ToolbarButton(wordCountButtonClass)); - const toolbarButton = ItemDetailsMap.ToolbarButton(wordCountButtonClass); - await toolbarButton.click(); const expectedCount = expectedContent.split(" ").length; await BrowserVerifyAlert(`Words count: ${expectedCount}`); } - static async ClickHtmlToolbarSitefinityVideos(): Promise { - const wordCountButtonClass = "k-i-Sitefinity-videos"; - await BrowserWaitForElement(ItemDetailsMap.ToolbarButton(wordCountButtonClass)); - const toolbarButton = ItemDetailsMap.ToolbarButton(wordCountButtonClass); - await toolbarButton.click(); - } - static async ExpandHtmlField(): Promise { - await BrowserWaitForElement(ItemDetailsMap.HtmlFieldExpander); - const htmlField = ItemDetailsMap.HtmlFieldExpander; - await htmlField.click(); + await BrowserWaitForElement(ItemDetailsMap.HtmlFieldExpandButton); + const htmlFieldExpandButton = ItemDetailsMap.HtmlFieldExpandButton; + await htmlFieldExpandButton.click(); } static async VerifyCustomTitleField(): Promise { - await BrowserWaitForElement(ItemDetailsMap.TitleField); + await BrowserWaitForElement(ItemDetailsMap.ExtendedTitleField); expect(await ItemDetailsMap.ExtendedTitleField.isPresent()).toBeTruthy("The title field extension class was not found"); } @@ -43,22 +32,26 @@ export class ItemDetails { } } - static async VerifyAndClickSymbolListButton(): Promise { - const symbolListButtonClass = "k-i-insertsymbol"; - await BrowserWaitForElement(ItemDetailsMap.ToolbarButton(symbolListButtonClass)); - const toolbarButton = ItemDetailsMap.ToolbarButton(symbolListButtonClass); + static async ClickToolbarButtonByTitle(buttonTitle: string): Promise { + await BrowserWaitForElement(ItemDetailsMap.ToolbarButtonByTitle(buttonTitle)); + const toolbarButton = ItemDetailsMap.ToolbarButtonByTitle(buttonTitle); await toolbarButton.click(); + } + + static async VerifyAndClickSymbolListButton(): Promise { await BrowserWaitForElement(EditorPopupMap.ToolPopup); - const symbolButton = EditorPopupMap.SymbolCell; const editor = ItemDetailsMap.EditorInternalField; + const contentsBeforeInsert = await editor.getText(); - const contents = await editor.getText(); + const symbolButton = EditorPopupMap.SymbolCell; await symbolButton.click(); + const contentAfterInsert = await editor.getText(); + // should hide the popup when symbol is clicked await BrowserWaitForElementHidden(EditorPopupMap.ToolPopup); // should have one more character after the symbol is inserted - expect(contents.length).toBe(contentAfterInsert.length - 1); + expect(contentAfterInsert.length).toBe(contentsBeforeInsert.length + 1); } } diff --git a/tests/e2e/app-elements/item-details.map.ts b/tests/e2e/app-elements/item-details.map.ts index 65416ead..4a77a9a6 100644 --- a/tests/e2e/app-elements/item-details.map.ts +++ b/tests/e2e/app-elements/item-details.map.ts @@ -1,16 +1,11 @@ import { element, by, ElementFinder } from "protractor"; -const defaultFieldLocator = ".sf-input.-rich-text"; -const itemTitleExtensionCssClass = ".custom-title-input"; - export class ItemDetailsMap { - public static TitleField: ElementFinder = element(by.css(defaultFieldLocator)); - public static ExtendedTitleField: ElementFinder = element(by.css(itemTitleExtensionCssClass)); - public static HtmlFieldExpander: ElementFinder = element(by.css(".sf-expand-button")); - public static EditorInternalField: ElementFinder = element(by.css(".k-editor.k-editor-inline")); - public static PublishButton: ElementFinder = element(by.cssContainingText("button", "Publish")); + public static ExtendedTitleField: ElementFinder = element(by.css(".custom-title-input")); + public static EditorInternalField: ElementFinder = element(by.cssContainingText("div", "Hello everyone")); + public static HtmlFieldExpandButton: ElementFinder = element(by.css("a[title=Expand]")); - public static ToolbarButton(customClass: string): ElementFinder { - return element(by.className(customClass)); + public static ToolbarButtonByTitle(buttonTitle: string): ElementFinder { + return element(by.css(`a[Title="${buttonTitle}"]`)) } } diff --git a/tests/e2e/app-elements/item-list.component.ts b/tests/e2e/app-elements/item-list.component.ts index 15936495..8b172d5d 100644 --- a/tests/e2e/app-elements/item-list.component.ts +++ b/tests/e2e/app-elements/item-list.component.ts @@ -1,63 +1,23 @@ require("jasmine-expect"); -import { removeQueryParams } from "../helpers/common"; import { ItemListMap } from "./item-list.map"; -import { BrowserWaitForElement, BrowserGetUrl } from "../helpers/browser-helpers"; -import { CONTENT_PAGE_URL, DEFAULT_ITEMS_NUMBER } from "../helpers/constants"; +import { BrowserWaitForElement } from "../helpers/browser-helpers"; export class ItemList { - - static async VerifyBasicUIElements(typeName: string, itemTitlePlural: string) { - await BrowserWaitForElement(ItemListMap.CountLabel); - const trimmedUrl = await removeQueryParams(BrowserGetUrl()); - const expectedUrl = CONTENT_PAGE_URL + typeName.toLowerCase(); - expect(trimmedUrl).toBe(expectedUrl); - expect(await ItemListMap.TitleTag.isDisplayed()).toBeTruthy(); - expect(await ItemListMap.TitleTag.getText()).toBe(itemTitlePlural); - } - - static async VerifyBasicGridElements(itemTitle: string, headers: string[], allItems: number, currentLoadedItems = DEFAULT_ITEMS_NUMBER) { - await BrowserWaitForElement(ItemListMap.TitleTag); - await ItemList.VerifyHeaders(headers); - expect(await ItemListMap.CountLabel.isDisplayed()).toBeTruthy(); - expect(await ItemListMap.TableElements.count()).toBe(currentLoadedItems); - - let expectedItemCountLabel: string; - if (allItems <= currentLoadedItems) { - expectedItemCountLabel = allItems + " " + itemTitle.toLowerCase(); - } else { - expectedItemCountLabel = DEFAULT_ITEMS_NUMBER + " from " + allItems + " " + itemTitle.toLowerCase(); - } - expectedItemCountLabel = (allItems > 1) ? `${expectedItemCountLabel}` : expectedItemCountLabel; - - expect(await ItemListMap.CountLabel.getText()).toBe(expectedItemCountLabel); + static async VerifyImageColumn() { + await BrowserWaitForElement(ItemListMap.ImageColumn); } - static async ClickPrintPreview(title: string) { - const actionButton = ItemListMap.GetItemActionsMenu(title); - + static async ClickPrintPreview() { + const actionButton = ItemListMap.GetItemActionsMenu(); await actionButton.click(); await BrowserWaitForElement(ItemListMap.PrintPreviewButton); await ItemListMap.PrintPreviewButton.click(); } static async ClickOnItem(title: string) { - await BrowserWaitForElement(ItemListMap.CountLabel); await BrowserWaitForElement(ItemListMap.GetRowTitleCell(title)); const item = ItemListMap.GetRowTitleCell(title); await item.click(); } - - private static async VerifyHeaders(headers: string[]) { - const headersNumber = headers.length; - try { - expect(await ItemListMap.TableHeaders.count()).toBe(headersNumber); - for (let i = 0; i < headersNumber; i++) { - const innerText = await ItemListMap.TableHeaders.get(i).getAttribute("innerText"); - expect(innerText.toUpperCase().trim()).toBe(headers[i].toUpperCase()); - } - } catch (e) { - throw new Error("Table headers are not correct."); - } - } } diff --git a/tests/e2e/app-elements/item-list.map.ts b/tests/e2e/app-elements/item-list.map.ts index 5f3c3fd5..83132ea0 100644 --- a/tests/e2e/app-elements/item-list.map.ts +++ b/tests/e2e/app-elements/item-list.map.ts @@ -1,24 +1,16 @@ -import { element, by, ElementFinder, ElementArrayFinder } from "protractor"; +import { element, by, ElementFinder } from "protractor"; export class ItemListMap { - public static TitleTag: ElementFinder = element.all(by.css(".sf-row h1")).last(); public static PrintPreviewButton: ElementFinder = element(by.cssContainingText("div[role=option]", "Print preview")); - public static CountLabel: ElementFinder = element(by.css("div.row__col.-txt-align-right.-txt-hint")); - public static TableHeaders: ElementArrayFinder = element(by.className("sf-tree-list__row -head")).all(by.className("sf-tree-list__cell -head")); - public static TableElements: ElementArrayFinder = element.all(by.css(".sf-tree-list__row:not(.-head):not(.-loading)")); - public static BackButton: ElementFinder = element(by.css(".sf-button.-toggle.-icon[title=Back]")); + public static ImageColumn: ElementFinder = element(by.cssContainingText(`div[data-sftest=image3]`, "Image")); + public static ActionsButton: ElementFinder = element(by.css("[title=Actions]")); + public static BackButton: ElementFinder = element(by.css("button[title=Back]")); public static GetRowTitleCell(rowTitle: string): ElementFinder { - const itemRow = ItemListMap.GetTableRow(rowTitle); - return itemRow.element(by.css(".sf-tree-list__cell.-title")); + return element(by.cssContainingText("p", rowTitle)); } - public static GetItemActionsMenu(rowTitle: string): ElementFinder { - const itemRow = ItemListMap.GetTableRow(rowTitle); - return itemRow.element(by.css("[title=Actions]")); - } - - private static GetTableRow(rowTitle: string): ElementFinder { - return element(by.cssContainingText(".tree-node-level-1", rowTitle)); + public static GetItemActionsMenu(): ElementFinder { + return this.ActionsButton; } } diff --git a/tests/e2e/app-specs/verify-extensions.e2e-spec.ts b/tests/e2e/app-specs/verify-extensions.e2e-spec.ts index b6b98a57..53723a78 100644 --- a/tests/e2e/app-specs/verify-extensions.e2e-spec.ts +++ b/tests/e2e/app-specs/verify-extensions.e2e-spec.ts @@ -1,16 +1,13 @@ import { initAuth } from "../helpers/authentication-manager"; import { ItemList } from "../app-elements/item-list.component"; -import { USERNAME, PASSWORD, TIMEOUT, DYNAMIC_ITEM_HEADERS, TABLE_HEADERS_CONSTANTS, CONTENT_NEWS_URL, NEWS_TYPE_NAME, SAMPLE_TEXT_CONTENT } from "../helpers/constants"; -import { BrowserNavigate, BrowserWaitForElement, SelectAllAndPasteText, BrowserVerifyConsoleOutput } from "../helpers/browser-helpers"; +import { USERNAME, PASSWORD, TIMEOUT, CONTENT_NEWS_URL, SAMPLE_TEXT_CONTENT } from "../helpers/constants"; +import { BrowserNavigate, SelectAllAndPasteText, BrowserVerifyConsoleOutput, BrowserWaitForElement } from "../helpers/browser-helpers"; import { PrintPreview } from "../app-elements/print-preview.component"; import { ItemDetails } from "../app-elements/item-details.component"; import { VideosModal } from "../app-elements/videos-modal.component"; -import { ItemListMap } from "../app-elements/item-list.map"; -import { ItemDetailsMap } from "../app-elements/item-details.map"; +import { ItemListMap } from '../app-elements/item-list.map'; describe("Verify extensions", () => { - const typeToTest = "News"; - const imageColumnHeader = "IMAGE"; const itemToVerify = "Building an Appointment Tracking App by using Telerik’s WP Cloud Components - Part 1"; beforeAll(async (done: DoneFn) => { @@ -20,48 +17,46 @@ describe("Verify extensions", () => { it("images column", async () => { await BrowserNavigate(CONTENT_NEWS_URL); - await ItemList.VerifyBasicUIElements(NEWS_TYPE_NAME, typeToTest); - - const extensionHeaders = DYNAMIC_ITEM_HEADERS.map((header) => { return header === TABLE_HEADERS_CONSTANTS.DATE_CREATED ? imageColumnHeader : header; }); - await ItemList.VerifyBasicGridElements(typeToTest, extensionHeaders, 34); + await ItemList.VerifyImageColumn(); }); - it("print preview ", async () => { - await ItemList.ClickPrintPreview(itemToVerify); + it("print preview", async () => { + await ItemList.ClickPrintPreview(); await PrintPreview.VerifyPrintPreview(itemToVerify); }); - it("custom title field ", async () => { + it("custom title field", async () => { await BrowserNavigate(CONTENT_NEWS_URL); await ItemList.ClickOnItem(itemToVerify); await ItemDetails.VerifyCustomTitleField(); }); - it("word count editor toolbar button ", async () => { + it("word count editor toolbar button", async () => { await ItemDetails.ExpandHtmlField(); await SelectAllAndPasteText(SAMPLE_TEXT_CONTENT); + await ItemDetails.ClickToolbarButtonByTitle("Words count"); await ItemDetails.VerifyHtmlToolbarWordCount(SAMPLE_TEXT_CONTENT); }); - it("word count editor toolbar button ", async () => { - await ItemDetails.ClickHtmlToolbarSitefinityVideos(); + it("embed video editor toolbar button", async () => { + await ItemDetails.ClickToolbarButtonByTitle("Sitefinity videos"); await VideosModal.VerifyModalTitle(); await VideosModal.CancelModal(); await ItemDetails.ClickBackButton(true); - await BrowserWaitForElement(ItemListMap.CountLabel); + await BrowserWaitForElement(ItemListMap.ImageColumn); }); it("insert symbol", async () => { await BrowserNavigate(CONTENT_NEWS_URL); await ItemList.ClickOnItem(itemToVerify); await ItemDetails.ExpandHtmlField(); + await ItemDetails.ClickToolbarButtonByTitle("Insert symbol"); await ItemDetails.VerifyAndClickSymbolListButton(); }); it("item hooks", async () => { await BrowserNavigate(CONTENT_NEWS_URL); await ItemList.ClickOnItem(itemToVerify); - await BrowserWaitForElement(ItemDetailsMap.PublishButton); await BrowserVerifyConsoleOutput(itemToVerify); }); }); From 6117eda7a89bd33c0841b1177c05f78b15f7795e Mon Sep 17 00:00:00 2001 From: Decho Dechev Date: Mon, 17 Dec 2018 09:01:57 +0200 Subject: [PATCH 07/32] Pinned the chromedriver version (#81) --- tests/e2e/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/package.json b/tests/e2e/package.json index d520b9f2..94bca804 100644 --- a/tests/e2e/package.json +++ b/tests/e2e/package.json @@ -4,7 +4,7 @@ "license": "private", "private": true, "scripts": { - "postinstall": "node ./node_modules/protractor/bin/webdriver-manager update --standalone", + "postinstall": "node ./node_modules/protractor/bin/webdriver-manager update --standalone --versions.chrome 2.42", "e2e": "node ./node_modules/protractor/bin/protractor", "e2e:headlessChrome": "npm run e2e -- --params.browser=chrome --params.headless" }, From fe98e0e7f8d7a0f34353c4b26a6f9eca0ca369a4 Mon Sep 17 00:00:00 2001 From: Decho Dechev Date: Fri, 21 Dec 2018 14:48:38 +0200 Subject: [PATCH 08/32] Fix e2e tests (#83) * fix e2e * Formatting fixes --- tests/e2e/app-elements/item-details.component.ts | 12 ++++++++---- tests/e2e/app-elements/item-details.map.ts | 1 + tests/e2e/app-elements/item-list.map.ts | 1 + tests/e2e/app-specs/verify-extensions.e2e-spec.ts | 4 +++- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/tests/e2e/app-elements/item-details.component.ts b/tests/e2e/app-elements/item-details.component.ts index 0cee6c2e..3cc77af1 100644 --- a/tests/e2e/app-elements/item-details.component.ts +++ b/tests/e2e/app-elements/item-details.component.ts @@ -1,6 +1,7 @@ require("jasmine-expect"); import { browser } from "protractor"; import { ItemDetailsMap } from "./item-details.map"; +import { EC, TIME_TO_WAIT } from "../helpers/constants"; import { BrowserWaitForElement, BrowserVerifyAlert, BrowserWaitForElementHidden } from "../helpers/browser-helpers"; import { EditorPopupMap } from "./editor-popup.map"; import { ItemListMap } from "./item-list.map"; @@ -23,12 +24,15 @@ export class ItemDetails { } static async ClickBackButton(acceptAlert: boolean = false): Promise { - await BrowserWaitForElement(ItemListMap.BackButton); - await ItemListMap.BackButton.click(); + await BrowserWaitForElement(ItemDetailsMap.BackButton); + await ItemDetailsMap.BackButton.click(); if (acceptAlert === true) { - const alert = browser.switchTo().alert(); - await alert.accept(); + browser.waitForAngularEnabled(false); + await browser.wait(EC.alertIsPresent(), TIME_TO_WAIT, "Expected alert is not shown."); + await browser.switchTo().alert().accept(); + await BrowserWaitForElement(ItemListMap.TitleTag); + browser.waitForAngularEnabled(true); } } diff --git a/tests/e2e/app-elements/item-details.map.ts b/tests/e2e/app-elements/item-details.map.ts index 4a77a9a6..6205cd17 100644 --- a/tests/e2e/app-elements/item-details.map.ts +++ b/tests/e2e/app-elements/item-details.map.ts @@ -2,6 +2,7 @@ import { element, by, ElementFinder } from "protractor"; export class ItemDetailsMap { public static ExtendedTitleField: ElementFinder = element(by.css(".custom-title-input")); + public static BackButton: ElementFinder = element(by.css(".sf-button.-toggle.-icon")); public static EditorInternalField: ElementFinder = element(by.cssContainingText("div", "Hello everyone")); public static HtmlFieldExpandButton: ElementFinder = element(by.css("a[title=Expand]")); diff --git a/tests/e2e/app-elements/item-list.map.ts b/tests/e2e/app-elements/item-list.map.ts index 83132ea0..015d0bf3 100644 --- a/tests/e2e/app-elements/item-list.map.ts +++ b/tests/e2e/app-elements/item-list.map.ts @@ -4,6 +4,7 @@ export class ItemListMap { public static PrintPreviewButton: ElementFinder = element(by.cssContainingText("div[role=option]", "Print preview")); public static ImageColumn: ElementFinder = element(by.cssContainingText(`div[data-sftest=image3]`, "Image")); public static ActionsButton: ElementFinder = element(by.css("[title=Actions]")); + public static TitleTag: ElementFinder = element.all(by.css(".sf-row h1")).last(); public static BackButton: ElementFinder = element(by.css("button[title=Back]")); public static GetRowTitleCell(rowTitle: string): ElementFinder { diff --git a/tests/e2e/app-specs/verify-extensions.e2e-spec.ts b/tests/e2e/app-specs/verify-extensions.e2e-spec.ts index 53723a78..b5cc74cc 100644 --- a/tests/e2e/app-specs/verify-extensions.e2e-spec.ts +++ b/tests/e2e/app-specs/verify-extensions.e2e-spec.ts @@ -5,7 +5,7 @@ import { BrowserNavigate, SelectAllAndPasteText, BrowserVerifyConsoleOutput, Bro import { PrintPreview } from "../app-elements/print-preview.component"; import { ItemDetails } from "../app-elements/item-details.component"; import { VideosModal } from "../app-elements/videos-modal.component"; -import { ItemListMap } from '../app-elements/item-list.map'; +import { ItemListMap } from "../app-elements/item-list.map"; describe("Verify extensions", () => { const itemToVerify = "Building an Appointment Tracking App by using Telerik’s WP Cloud Components - Part 1"; @@ -52,6 +52,8 @@ describe("Verify extensions", () => { await ItemDetails.ExpandHtmlField(); await ItemDetails.ClickToolbarButtonByTitle("Insert symbol"); await ItemDetails.VerifyAndClickSymbolListButton(); + await ItemDetails.ClickBackButton(true); + await BrowserWaitForElement(ItemListMap.ImageColumn); }); it("item hooks", async () => { From 80b9182fbe8662f3db659f4b9ab7c1455f447e6c Mon Sep 17 00:00:00 2001 From: Nikolai Novkirishki Date: Fri, 18 Jan 2019 12:37:16 +0200 Subject: [PATCH 09/32] wip --- README.md | 2 +- package-lock.json | 13 ++++------- tests/e2e/app-elements/theme.component.ts | 22 +++++++++++++++++++ tests/e2e/app-elements/theme.map.ts | 9 ++++++++ .../app-specs/verify-extensions.e2e-spec.ts | 9 ++++++++ tests/e2e/helpers/constants.ts | 1 + 6 files changed, 46 insertions(+), 10 deletions(-) create mode 100644 tests/e2e/app-elements/theme.component.ts create mode 100644 tests/e2e/app-elements/theme.map.ts diff --git a/README.md b/README.md index 0191504a..568836b8 100644 --- a/README.md +++ b/README.md @@ -241,4 +241,4 @@ You can customize the appearance of the Admin App by modifying specific componen ### Access data from OData services -You can HTTP calls to Sitefinity CMS OData services via the Angular **HttpClient**. When you make the request, you use the HTTP_PREFIX constant, so that the Admin App automatically detects this is a request to Sitefinity CMS and completes the request accordingly. +You can make HTTP calls to Sitefinity CMS OData services via the Angular **HttpClient**. When you make the request, you use the HTTP_PREFIX constant, so that the Admin App automatically detects this is a request to Sitefinity CMS and completes the request accordingly. diff --git a/package-lock.json b/package-lock.json index a780a518..2c524310 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2230,8 +2230,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -2633,8 +2632,7 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -2689,7 +2687,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2733,14 +2730,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, diff --git a/tests/e2e/app-elements/theme.component.ts b/tests/e2e/app-elements/theme.component.ts new file mode 100644 index 00000000..f8b958e9 --- /dev/null +++ b/tests/e2e/app-elements/theme.component.ts @@ -0,0 +1,22 @@ +require("jasmine-expect"); + +import { ThemeMap } from "./theme.map"; +import { BrowserWaitForElement } from "../helpers/browser-helpers"; +import { EC } from "../helpers/constants"; +import { browser } from "protractor"; + +const timeToWait = 3000; + +export class Theme { + public static async SelectTheme(themeName: string) { + await BrowserWaitForElement(ThemeMap.UseSelectedButton); + const item = ThemeMap.GetThemeRow(themeName); + await item.click(); + } + + public static async UseSelectedTheme() { + await BrowserWaitForElement(ThemeMap.UseSelectedButton); + await ThemeMap.UseSelectedButton.click(); + await browser.wait(EC.elementToBeClickable(ThemeMap.UseSelectedButton), timeToWait); + } +} diff --git a/tests/e2e/app-elements/theme.map.ts b/tests/e2e/app-elements/theme.map.ts new file mode 100644 index 00000000..7c771712 --- /dev/null +++ b/tests/e2e/app-elements/theme.map.ts @@ -0,0 +1,9 @@ +import { element, by, ElementFinder } from "protractor"; + +export class ThemeMap { + public static UseSelectedButton: ElementFinder = element(by.cssContainingText(".sf-button", "Use selected")); + + public static GetThemeRow(themeName: string): ElementFinder { + return element(by.cssContainingText(".tree-node-level-1", themeName)); + } +} diff --git a/tests/e2e/app-specs/verify-extensions.e2e-spec.ts b/tests/e2e/app-specs/verify-extensions.e2e-spec.ts index c85e6ddb..17a53c16 100644 --- a/tests/e2e/app-specs/verify-extensions.e2e-spec.ts +++ b/tests/e2e/app-specs/verify-extensions.e2e-spec.ts @@ -64,6 +64,15 @@ describe("Verify extensions", () => { await ItemDetails.ClickBackButton(true); }); + it("applied theme", async () => { + await BrowserNavigate(THEME_URL); + await Theme.SelectTheme("Sample"); + await Theme.UseSelectedTheme(); + + await BrowserNavigate(CONTENT_NEWS_URL); + await ItemList.VerifyThemeButtonColor(); + }); + it("item hooks", async () => { await BrowserNavigate(CONTENT_NEWS_URL); await ItemList.ClickOnItem(itemToVerify); diff --git a/tests/e2e/helpers/constants.ts b/tests/e2e/helpers/constants.ts index 7fb34a15..e6da754c 100644 --- a/tests/e2e/helpers/constants.ts +++ b/tests/e2e/helpers/constants.ts @@ -10,6 +10,7 @@ export const CONFIG_PAGE_URL = `${BASE_URL}config`; export const CONTENT_PAGE_URL = `${BASE_URL}content/`; export const CONTENT_BLOGS_PAGE_URL = `${BASE_URL}content/blogs`; export const CONTENT_NEWS_URL = `${BASE_URL}content/newsitems`; +export const THEME_URL = `${BASE_URL}theme`; export const EC = protractor.ExpectedConditions; export const URL_IN_CONFIG_FILE = browser.params.sfUrl; From 9dfdee2e6fcb1df2a63e66aa677fba690a6f58af Mon Sep 17 00:00:00 2001 From: Nikolai Novkirishki Date: Fri, 18 Jan 2019 12:38:45 +0200 Subject: [PATCH 10/32] wip --- tests/e2e/app-elements/theme.component.ts | 2 +- tests/e2e/app-elements/theme.map.ts | 2 +- tests/e2e/app-specs/verify-extensions.e2e-spec.ts | 6 ------ 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/tests/e2e/app-elements/theme.component.ts b/tests/e2e/app-elements/theme.component.ts index f8b958e9..b50b9a92 100644 --- a/tests/e2e/app-elements/theme.component.ts +++ b/tests/e2e/app-elements/theme.component.ts @@ -14,7 +14,7 @@ export class Theme { await item.click(); } - public static async UseSelectedTheme() { + public static async UseSelectedTheme() { await BrowserWaitForElement(ThemeMap.UseSelectedButton); await ThemeMap.UseSelectedButton.click(); await browser.wait(EC.elementToBeClickable(ThemeMap.UseSelectedButton), timeToWait); diff --git a/tests/e2e/app-elements/theme.map.ts b/tests/e2e/app-elements/theme.map.ts index 7c771712..7c2bf245 100644 --- a/tests/e2e/app-elements/theme.map.ts +++ b/tests/e2e/app-elements/theme.map.ts @@ -3,7 +3,7 @@ import { element, by, ElementFinder } from "protractor"; export class ThemeMap { public static UseSelectedButton: ElementFinder = element(by.cssContainingText(".sf-button", "Use selected")); - public static GetThemeRow(themeName: string): ElementFinder { + public static GetThemeRow(themeName: string): ElementFinder { return element(by.cssContainingText(".tree-node-level-1", themeName)); } } diff --git a/tests/e2e/app-specs/verify-extensions.e2e-spec.ts b/tests/e2e/app-specs/verify-extensions.e2e-spec.ts index 17a53c16..42052580 100644 --- a/tests/e2e/app-specs/verify-extensions.e2e-spec.ts +++ b/tests/e2e/app-specs/verify-extensions.e2e-spec.ts @@ -73,12 +73,6 @@ describe("Verify extensions", () => { await ItemList.VerifyThemeButtonColor(); }); - it("item hooks", async () => { - await BrowserNavigate(CONTENT_NEWS_URL); - await ItemList.ClickOnItem(itemToVerify); - await BrowserVerifyConsoleOutput(itemToVerify); - }); - xit("spell checker", async () => { await BrowserNavigate(CONTENT_NEWS_URL); await ItemList.ClickOnItem(itemToVerify); From f70424b640d296fa07ed6d65645f29a657bea2c8 Mon Sep 17 00:00:00 2001 From: Nikolai Novkirishki Date: Fri, 18 Jan 2019 14:07:59 +0200 Subject: [PATCH 11/32] wip --- .../address-custom-field-provider.ts | 84 +++++++++++++++++++ .../address-custom-field.component.html | 12 +++ .../address-custom-field.component.ts | 8 ++ custom-fields/index.ts | 11 ++- 4 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 custom-fields/address-custom-field/address-custom-field-provider.ts create mode 100644 custom-fields/address-custom-field/address-custom-field.component.html create mode 100644 custom-fields/address-custom-field/address-custom-field.component.ts diff --git a/custom-fields/address-custom-field/address-custom-field-provider.ts b/custom-fields/address-custom-field/address-custom-field-provider.ts new file mode 100644 index 00000000..8094ae09 --- /dev/null +++ b/custom-fields/address-custom-field/address-custom-field-provider.ts @@ -0,0 +1,84 @@ +import { Injectable, ClassProvider } from "@angular/core"; + +import { FIELDS_PROVIDER_TOKEN, FieldData, FieldsProvider, SettingsBase } from "progress-sitefinity-adminapp-sdk/app/api/v1"; +import { FieldRegistration } from "progress-sitefinity-adminapp-sdk/app/api/v1"; +import { RegistrationPair } from "./../registration-pair"; +import { AddressCustomFieldComponent } from "./address-custom-field.component"; + +/** + * The fields provider provides the overridden fields back to the AdminApp. + */ +@Injectable() +export class AddressCustomFieldProvider implements FieldsProvider { + private customFieldsMappings: RegistrationPair[]; + + constructor() { + this.customFieldsMappings = []; + + this.registerCustomComponents(); + } + + /** + * This method gets called before each field is instantiated, allowing custom fields to be plugged in for any type. + * @param fieldRegistryKey The metadata needed to determine which field to display. + */ + overrideField(fieldRegistryKey: FieldData): FieldRegistration { + const registration: FieldRegistration = this.findRegistration(fieldRegistryKey); + return registration; + } + + /** + * This method finds an implementation of the field to be overridden. + * @param fieldRegistryKey The metadata needed to determine which field to display. + */ + private findRegistration(fieldRegistryKey: FieldData): FieldRegistration { + for (const pair of this.customFieldsMappings) { + if (fieldRegistryKey.fieldName === pair.key.fieldName && + fieldRegistryKey.fieldType === pair.key.fieldType && + fieldRegistryKey.typeName === pair.key.typeName) { + return pair.registration; + } + } + + return null; + } + + /** + * Initializes the custom field(component) registrations. + */ + private registerCustomComponents(): void { + + // The field name is the name which identifies the field uniquely. + // The typename is the OData entity set name. It matches the url segment when navigating + // to the list view of the specific type. + const customInputKey: FieldData = { + fieldName: "CustomAddress", + fieldType: "sf-short-text-default", + typeName: "newsitems" + }; + + // The result field registration that will be returned to the AdminApp. + const customInputRegistration: FieldRegistration = { + writeComponent: AddressCustomFieldComponent, + readComponent: AddressCustomFieldComponent, + settingsType: SettingsBase + }; + + const customFieldRegistrationPair: RegistrationPair = { + key: customInputKey, + registration: customInputRegistration + }; + + this.customFieldsMappings.push(customFieldRegistrationPair); + } +} + +/** + * Export a 'multi' class provider so that multiple instances of the same provider can coexist. + * This allows for more than one provider to be registered within one or more bundles. + */ +export const ADDRESS_CUSTOM_FIELDS_PROVIDER: ClassProvider = { + provide: FIELDS_PROVIDER_TOKEN, + useClass: AddressCustomFieldProvider, + multi: true +}; diff --git a/custom-fields/address-custom-field/address-custom-field.component.html b/custom-fields/address-custom-field/address-custom-field.component.html new file mode 100644 index 00000000..f3267400 --- /dev/null +++ b/custom-fields/address-custom-field/address-custom-field.component.html @@ -0,0 +1,12 @@ + + +bace diff --git a/custom-fields/address-custom-field/address-custom-field.component.ts b/custom-fields/address-custom-field/address-custom-field.component.ts new file mode 100644 index 00000000..6504ffdc --- /dev/null +++ b/custom-fields/address-custom-field/address-custom-field.component.ts @@ -0,0 +1,8 @@ +import { Component } from "@angular/core"; +import { FieldBase } from "progress-sitefinity-adminapp-sdk/app/api/v1"; + +@Component({ + templateUrl: "./address-custom-field.component.html" +}) +export class AddressCustomFieldComponent extends FieldBase { +} diff --git a/custom-fields/index.ts b/custom-fields/index.ts index 88c02bc6..9a1625a6 100644 --- a/custom-fields/index.ts +++ b/custom-fields/index.ts @@ -5,6 +5,8 @@ import { CustomInputReadonlyComponent } from "./custom-field-readonly.component" import { CustomInputWriteComponent } from "./custom-field-write.component"; import { CUSTOM_FIELDS_PROVIDER } from "./custom-fields-provider"; import { FrameworkModule } from "progress-sitefinity-adminapp-sdk/app/api/v1"; +// import { AddressCustomFieldComponent } from "./address-custom-field/address-custom-field.component"; +// import { ADDRESS_CUSTOM_FIELDS_PROVIDER } from "./address-custom-field/address-custom-field-provider"; /** * The custom fields module. @@ -12,15 +14,18 @@ import { FrameworkModule } from "progress-sitefinity-adminapp-sdk/app/api/v1"; @NgModule({ declarations: [ CustomInputReadonlyComponent, - CustomInputWriteComponent + CustomInputWriteComponent, + // AddressCustomFieldComponent ], entryComponents: [ // The components need to be registered here as they are instantiated dynamically. CustomInputReadonlyComponent, - CustomInputWriteComponent + CustomInputWriteComponent, + // AddressCustomFieldComponent ], providers: [ - CUSTOM_FIELDS_PROVIDER + CUSTOM_FIELDS_PROVIDER, + // ADDRESS_CUSTOM_FIELDS_PROVIDER ], // import the framework module as it holds the components that the AdminApp uses From fc2c87ac5da5d964546d6fa38d9687648d4a99f6 Mon Sep 17 00:00:00 2001 From: Nikolai Novkirishki Date: Fri, 18 Jan 2019 16:30:02 +0200 Subject: [PATCH 12/32] Make calls to API to get suggestions --- .../address-custom-field-provider.ts | 2 +- .../address-custom-field.component.ts | 39 +++++++++++++++++++ custom-fields/index.ts | 10 ++--- 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/custom-fields/address-custom-field/address-custom-field-provider.ts b/custom-fields/address-custom-field/address-custom-field-provider.ts index 8094ae09..f200d630 100644 --- a/custom-fields/address-custom-field/address-custom-field-provider.ts +++ b/custom-fields/address-custom-field/address-custom-field-provider.ts @@ -53,7 +53,7 @@ export class AddressCustomFieldProvider implements FieldsProvider { // to the list view of the specific type. const customInputKey: FieldData = { fieldName: "CustomAddress", - fieldType: "sf-short-text-default", + fieldType: "sf-short-text", typeName: "newsitems" }; diff --git a/custom-fields/address-custom-field/address-custom-field.component.ts b/custom-fields/address-custom-field/address-custom-field.component.ts index 6504ffdc..85a3b5da 100644 --- a/custom-fields/address-custom-field/address-custom-field.component.ts +++ b/custom-fields/address-custom-field/address-custom-field.component.ts @@ -1,8 +1,47 @@ import { Component } from "@angular/core"; import { FieldBase } from "progress-sitefinity-adminapp-sdk/app/api/v1"; +import { HttpClient, HttpParams } from "@angular/common/http"; +import { Observable } from "rxjs"; + +/* + * NOTE: Replace this example keys with your subscription keys. + * For more information on how to get a key check here: https://developer.here.com/?create=Freemium-Basic&keepState=true&step=terms + */ +const HERE_MAPS_APP_ID = 'iV0wv8ievlwH8Fd5Raii'; +const HERE_MAPS_APP_CODE = 'AHNPEuMJkSuNjkP7SpW2xg'; + +const HOST = 'http://autocomplete.geocoder.api.here.com'; +const PATH = '/6.2/suggest.json'; @Component({ templateUrl: "./address-custom-field.component.html" }) export class AddressCustomFieldComponent extends FieldBase { + constructor(private http: HttpClient) { + super(); + } + + writeValue(value: any): void { + // The null check is required because of the initial load of the field + if (this.getValue() !== null && this.getValue() !== value) { + console.log(value); + this.makeRequest(value).subscribe((result) => { + console.log(result); + }); + } + + super.writeValue(value); + } + + private makeRequest(queryText: string): Observable { + let queryParams = new HttpParams(); + queryParams = queryParams.append('app_id', HERE_MAPS_APP_ID); + queryParams = queryParams.append('app_code', HERE_MAPS_APP_CODE); + queryParams = queryParams.append('query', queryText); + queryParams = queryParams.append('beginHighlight', ''); + queryParams = queryParams.append('endHighlight', ''); + + const url = HOST + PATH; + return this.http.get(url, { params: queryParams }); + } } diff --git a/custom-fields/index.ts b/custom-fields/index.ts index 9a1625a6..95b9858a 100644 --- a/custom-fields/index.ts +++ b/custom-fields/index.ts @@ -5,8 +5,8 @@ import { CustomInputReadonlyComponent } from "./custom-field-readonly.component" import { CustomInputWriteComponent } from "./custom-field-write.component"; import { CUSTOM_FIELDS_PROVIDER } from "./custom-fields-provider"; import { FrameworkModule } from "progress-sitefinity-adminapp-sdk/app/api/v1"; -// import { AddressCustomFieldComponent } from "./address-custom-field/address-custom-field.component"; -// import { ADDRESS_CUSTOM_FIELDS_PROVIDER } from "./address-custom-field/address-custom-field-provider"; +import { AddressCustomFieldComponent } from "./address-custom-field/address-custom-field.component"; +import { ADDRESS_CUSTOM_FIELDS_PROVIDER } from "./address-custom-field/address-custom-field-provider"; /** * The custom fields module. @@ -15,17 +15,17 @@ import { FrameworkModule } from "progress-sitefinity-adminapp-sdk/app/api/v1"; declarations: [ CustomInputReadonlyComponent, CustomInputWriteComponent, - // AddressCustomFieldComponent + AddressCustomFieldComponent ], entryComponents: [ // The components need to be registered here as they are instantiated dynamically. CustomInputReadonlyComponent, CustomInputWriteComponent, - // AddressCustomFieldComponent + AddressCustomFieldComponent ], providers: [ CUSTOM_FIELDS_PROVIDER, - // ADDRESS_CUSTOM_FIELDS_PROVIDER + ADDRESS_CUSTOM_FIELDS_PROVIDER ], // import the framework module as it holds the components that the AdminApp uses From 3076e9a49cf373a77e27278233199e985d625973 Mon Sep 17 00:00:00 2001 From: Nikolai Novkirishki Date: Fri, 18 Jan 2019 17:14:09 +0200 Subject: [PATCH 13/32] Add tree to show suggestions --- .../address-custom-field.component.html | 46 +++++++++++---- .../address-custom-field.component.ts | 57 ++++++++++++++----- custom-fields/index.ts | 3 +- 3 files changed, 79 insertions(+), 27 deletions(-) diff --git a/custom-fields/address-custom-field/address-custom-field.component.html b/custom-fields/address-custom-field/address-custom-field.component.html index f3267400..5288099b 100644 --- a/custom-fields/address-custom-field/address-custom-field.component.html +++ b/custom-fields/address-custom-field/address-custom-field.component.html @@ -1,12 +1,34 @@ - - -bace + + + + +
+
+ No results found +
+ + +
+
+
+
diff --git a/custom-fields/address-custom-field/address-custom-field.component.ts b/custom-fields/address-custom-field/address-custom-field.component.ts index 85a3b5da..7595d0f8 100644 --- a/custom-fields/address-custom-field/address-custom-field.component.ts +++ b/custom-fields/address-custom-field/address-custom-field.component.ts @@ -1,7 +1,9 @@ -import { Component } from "@angular/core"; +import { Component, OnInit } from "@angular/core"; import { FieldBase } from "progress-sitefinity-adminapp-sdk/app/api/v1"; import { HttpClient, HttpParams } from "@angular/common/http"; -import { Observable } from "rxjs"; +import { Observable, BehaviorSubject } from "rxjs"; +import { map } from "rxjs/operators"; + /* * NOTE: Replace this example keys with your subscription keys. @@ -16,30 +18,57 @@ const PATH = '/6.2/suggest.json'; @Component({ templateUrl: "./address-custom-field.component.html" }) -export class AddressCustomFieldComponent extends FieldBase { +export class AddressCustomFieldComponent extends FieldBase implements OnInit { + popupTreeConfig: any; + searchTerm: string; + hasSuggestions: Observable; + isPopupVisible: boolean = false; + + private _suggestionsSubject$: BehaviorSubject; + private _suggestions$: Observable; + constructor(private http: HttpClient) { super(); + + this._suggestionsSubject$ = new BehaviorSubject([]); + this._suggestions$ = this._suggestionsSubject$.asObservable(); + this.hasSuggestions = this._suggestions$.pipe(map((arr) => { console.log(arr); return arr.length > 0;})); + } + + get suggestions$(): Observable { + return this._suggestions$; } - writeValue(value: any): void { - // The null check is required because of the initial load of the field - if (this.getValue() !== null && this.getValue() !== value) { - console.log(value); - this.makeRequest(value).subscribe((result) => { - console.log(result); - }); + ngOnInit(): void { + this.popupTreeConfig = { + noSelection: true + }; + + this.searchTerm = this.getValue(); + } + + onFocus(): void { + if (this.searchTerm !== null) { + this.isPopupVisible = true; } + } + + onFocusOut(): void { + this.isPopupVisible = false; + } - super.writeValue(value); + onNewInputValue(event: Event): void { + this.getSuggestions(this.searchTerm).subscribe((result: any) => { + this._suggestionsSubject$.next(result.suggestions); + console.log(result); + }); } - private makeRequest(queryText: string): Observable { + private getSuggestions(queryText: string): Observable { let queryParams = new HttpParams(); queryParams = queryParams.append('app_id', HERE_MAPS_APP_ID); queryParams = queryParams.append('app_code', HERE_MAPS_APP_CODE); queryParams = queryParams.append('query', queryText); - queryParams = queryParams.append('beginHighlight', ''); - queryParams = queryParams.append('endHighlight', ''); const url = HOST + PATH; return this.http.get(url, { params: queryParams }); diff --git a/custom-fields/index.ts b/custom-fields/index.ts index 95b9858a..9ed82bc0 100644 --- a/custom-fields/index.ts +++ b/custom-fields/index.ts @@ -1,5 +1,6 @@ import { NgModule } from "@angular/core"; import { FormsModule } from "@angular/forms"; +import { CommonModule } from "@angular/common"; import { CustomInputReadonlyComponent } from "./custom-field-readonly.component"; import { CustomInputWriteComponent } from "./custom-field-write.component"; @@ -30,6 +31,6 @@ import { ADDRESS_CUSTOM_FIELDS_PROVIDER } from "./address-custom-field/address-c // import the framework module as it holds the components that the AdminApp uses // for a list of components see - imports: [FormsModule, FrameworkModule] + imports: [FormsModule, FrameworkModule, CommonModule] }) export class CustomFieldsModule { } From 3fa62513e7e0f10d480ec6d96a8eef6943055789 Mon Sep 17 00:00:00 2001 From: Nikolai Novkirishki Date: Mon, 13 May 2019 11:15:35 +0300 Subject: [PATCH 14/32] wip --- .../address-custom-field/address-custom-field-provider.ts | 2 +- package.json | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/custom-fields/address-custom-field/address-custom-field-provider.ts b/custom-fields/address-custom-field/address-custom-field-provider.ts index f200d630..170df1de 100644 --- a/custom-fields/address-custom-field/address-custom-field-provider.ts +++ b/custom-fields/address-custom-field/address-custom-field-provider.ts @@ -54,7 +54,7 @@ export class AddressCustomFieldProvider implements FieldsProvider { const customInputKey: FieldData = { fieldName: "CustomAddress", fieldType: "sf-short-text", - typeName: "newsitems" + typeName: "test" }; // The result field registration that will be returned to the AdminApp. diff --git a/package.json b/package.json index 9f1dac01..fb59ec31 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,6 @@ "start": "webpack-dev-server --config config/webpack.config.js --env.dev", "lint": "tslint --project ./tsconfig.json", "lint:fix": "tslint --fix --project ./tsconfig.json" - }, "keywords": [ "extensions", From 85d91bbc23467138ff5c75562236af0823962977 Mon Sep 17 00:00:00 2001 From: Nikolai Novkirishki Date: Mon, 13 May 2019 13:47:09 +0300 Subject: [PATCH 15/32] persistance --- .../address-custom-field-provider.ts | 4 +- .../address-custom-field.component.html | 2 +- .../address-custom-field.component.ts | 43 +++++++++++++++++-- 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/custom-fields/address-custom-field/address-custom-field-provider.ts b/custom-fields/address-custom-field/address-custom-field-provider.ts index 170df1de..6913988e 100644 --- a/custom-fields/address-custom-field/address-custom-field-provider.ts +++ b/custom-fields/address-custom-field/address-custom-field-provider.ts @@ -53,8 +53,8 @@ export class AddressCustomFieldProvider implements FieldsProvider { // to the list view of the specific type. const customInputKey: FieldData = { fieldName: "CustomAddress", - fieldType: "sf-short-text", - typeName: "test" + fieldType: "sf-text-area", + typeName: "tests" }; // The result field registration that will be returned to the AdminApp. diff --git a/custom-fields/address-custom-field/address-custom-field.component.html b/custom-fields/address-custom-field/address-custom-field.component.html index 5288099b..60ad8bc8 100644 --- a/custom-fields/address-custom-field/address-custom-field.component.html +++ b/custom-fields/address-custom-field/address-custom-field.component.html @@ -3,7 +3,7 @@ (contentFocusOut)="onFocusOut()"> ; @@ -32,7 +34,7 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { this._suggestionsSubject$ = new BehaviorSubject([]); this._suggestions$ = this._suggestionsSubject$.asObservable(); - this.hasSuggestions = this._suggestions$.pipe(map((arr) => { console.log(arr); return arr.length > 0;})); + this.hasSuggestions = this._suggestions$.pipe(map((arr) => { return arr.length > 0;})); } get suggestions$(): Observable { @@ -60,8 +62,32 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { onNewInputValue(event: Event): void { this.getSuggestions(this.searchTerm).subscribe((result: any) => { this._suggestionsSubject$.next(result.suggestions); - console.log(result); }); + + setTimeout(() => { + if (!this.isPopupVisible) { + this.isPopupVisible = true; + } + }, 300); + } + + onNewItemSelected(event) { + this.writeValue(JSON.stringify(event.data)); + } + + writeValue(value) { + if (this.isJSON(value)) { + const realValue = JSON.parse(value); + + if (realValue.label) { + this.streetAddress.nativeElement.value = realValue.label; + this.searchTerm = realValue.label; + } + + super.writeValue(realValue); + } else { + super.writeValue(value); + } } private getSuggestions(queryText: string): Observable { @@ -73,4 +99,13 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { const url = HOST + PATH; return this.http.get(url, { params: queryParams }); } + + private isJSON(value: string) { + try { + JSON.parse(value); + } catch (e) { + return false; + } + return true; + } } From d602ab699731312fff2c510843ca282ca05984af Mon Sep 17 00:00:00 2001 From: Nikolai Novkirishki Date: Mon, 13 May 2019 13:51:38 +0300 Subject: [PATCH 16/32] fix --- .../address-custom-field/address-custom-field.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom-fields/address-custom-field/address-custom-field.component.ts b/custom-fields/address-custom-field/address-custom-field.component.ts index f6759502..c9aba43e 100644 --- a/custom-fields/address-custom-field/address-custom-field.component.ts +++ b/custom-fields/address-custom-field/address-custom-field.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit, ViewChild } from "@angular/core"; -import { FieldBase, InputComponent } from "progress-sitefinity-adminapp-sdk/app/api/v1"; +import { FieldBase } from "progress-sitefinity-adminapp-sdk/app/api/v1"; import { HttpClient, HttpParams } from "@angular/common/http"; import { Observable, BehaviorSubject } from "rxjs"; import { map } from "rxjs/operators"; From 595e8e6c71bcafe1a3a296043f748014d34cdb1a Mon Sep 17 00:00:00 2001 From: Nikolai Novkirishki Date: Mon, 13 May 2019 14:02:31 +0300 Subject: [PATCH 17/32] write value fix --- .../address-custom-field.component.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/custom-fields/address-custom-field/address-custom-field.component.ts b/custom-fields/address-custom-field/address-custom-field.component.ts index c9aba43e..f5bc4ee8 100644 --- a/custom-fields/address-custom-field/address-custom-field.component.ts +++ b/custom-fields/address-custom-field/address-custom-field.component.ts @@ -76,18 +76,17 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { } writeValue(value) { - if (this.isJSON(value)) { + debugger + if (value && this.isJSON(value)) { const realValue = JSON.parse(value); if (realValue.label) { this.streetAddress.nativeElement.value = realValue.label; this.searchTerm = realValue.label; } - - super.writeValue(realValue); - } else { - super.writeValue(value); } + + super.writeValue(value); } private getSuggestions(queryText: string): Observable { From f42bc90586eb9c3d8baffb7014a81cf45792136a Mon Sep 17 00:00:00 2001 From: Elena Ganeva Date: Mon, 13 May 2019 16:05:48 +0300 Subject: [PATCH 18/32] Added address field for widget --- .../address-custom-field-provider.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/custom-fields/address-custom-field/address-custom-field-provider.ts b/custom-fields/address-custom-field/address-custom-field-provider.ts index 6913988e..c278821a 100644 --- a/custom-fields/address-custom-field/address-custom-field-provider.ts +++ b/custom-fields/address-custom-field/address-custom-field-provider.ts @@ -16,6 +16,7 @@ export class AddressCustomFieldProvider implements FieldsProvider { this.customFieldsMappings = []; this.registerCustomComponents(); + this.registerCustomWidgetComponents(); } /** @@ -71,6 +72,32 @@ export class AddressCustomFieldProvider implements FieldsProvider { this.customFieldsMappings.push(customFieldRegistrationPair); } + + private registerCustomWidgetComponents(): void { + + // The field name is the name which identifies the field uniquely. + // The typename is the OData entity set name. It matches the url segment when navigating + // to the list view of the specific type. + const customInputKey: FieldData = { + fieldName: "Address", + fieldType: "sf-short-text", + typeName: "widget-HereAddress" + }; + + // The result field registration that will be returned to the AdminApp. + const customInputRegistration: FieldRegistration = { + writeComponent: AddressCustomFieldComponent, + readComponent: AddressCustomFieldComponent, + settingsType: SettingsBase + }; + + const customFieldRegistrationPair: RegistrationPair = { + key: customInputKey, + registration: customInputRegistration + }; + + this.customFieldsMappings.push(customFieldRegistrationPair); + } } /** From 6596a72b214ded8fdd614dad5ccf72f4708b8825 Mon Sep 17 00:00:00 2001 From: Nikolai Novkirishki Date: Mon, 13 May 2019 23:37:40 +0300 Subject: [PATCH 19/32] Make map interactive, add balloon to map --- .../address-custom-field.component.html | 2 + .../address-custom-field.component.ts | 142 +++++++++++++++++- .../address-custom-field/script-service.ts | 77 ++++++++++ custom-fields/index.ts | 5 +- 4 files changed, 218 insertions(+), 8 deletions(-) create mode 100644 custom-fields/address-custom-field/script-service.ts diff --git a/custom-fields/address-custom-field/address-custom-field.component.html b/custom-fields/address-custom-field/address-custom-field.component.html index 60ad8bc8..90c66662 100644 --- a/custom-fields/address-custom-field/address-custom-field.component.html +++ b/custom-fields/address-custom-field/address-custom-field.component.html @@ -32,3 +32,5 @@ + +
diff --git a/custom-fields/address-custom-field/address-custom-field.component.ts b/custom-fields/address-custom-field/address-custom-field.component.ts index f5bc4ee8..e8dc69b3 100644 --- a/custom-fields/address-custom-field/address-custom-field.component.ts +++ b/custom-fields/address-custom-field/address-custom-field.component.ts @@ -1,9 +1,9 @@ -import { Component, OnInit, ViewChild } from "@angular/core"; +import { Component, OnInit, ViewChild, ElementRef } from "@angular/core"; import { FieldBase } from "progress-sitefinity-adminapp-sdk/app/api/v1"; import { HttpClient, HttpParams } from "@angular/common/http"; import { Observable, BehaviorSubject } from "rxjs"; import { map } from "rxjs/operators"; - +import { DynamicScriptLoaderService } from "./script-service"; /* * NOTE: Replace this example keys with your subscription keys. @@ -15,11 +15,14 @@ const HERE_MAPS_APP_CODE = 'AHNPEuMJkSuNjkP7SpW2xg'; const HOST = 'http://autocomplete.geocoder.api.here.com'; const PATH = '/6.2/suggest.json'; +declare var H: any; + @Component({ templateUrl: "./address-custom-field.component.html" }) export class AddressCustomFieldComponent extends FieldBase implements OnInit { @ViewChild("streetAddress") streetAddress: any; + @ViewChild("mapContainer") mapContainer: ElementRef; popupTreeConfig: any; searchTerm: string; @@ -29,7 +32,14 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { private _suggestionsSubject$: BehaviorSubject; private _suggestions$: Observable; - constructor(private http: HttpClient) { + private hereMap: any; + private herePlatform: any; + private hereGeocoder: any; + private hereGroup: any; + private hereUI: any; + private hereBubble: any; + + constructor(private http: HttpClient, private dynamicScriptLoader: DynamicScriptLoaderService) { super(); this._suggestionsSubject$ = new BehaviorSubject([]); @@ -47,11 +57,13 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { }; this.searchTerm = this.getValue(); + this.loadScripts(); } onFocus(): void { if (this.searchTerm !== null) { this.isPopupVisible = true; + this.updateSuggestions(); } } @@ -60,9 +72,8 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { } onNewInputValue(event: Event): void { - this.getSuggestions(this.searchTerm).subscribe((result: any) => { - this._suggestionsSubject$.next(result.suggestions); - }); + this.clearOldSuggestions(); + this.updateSuggestions(); setTimeout(() => { if (!this.isPopupVisible) { @@ -76,7 +87,6 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { } writeValue(value) { - debugger if (value && this.isJSON(value)) { const realValue = JSON.parse(value); @@ -84,11 +94,129 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { this.streetAddress.nativeElement.value = realValue.label; this.searchTerm = realValue.label; } + + // TODO: return lat & long and add them to object + this.addSuggestionToMap(realValue.locationId); } super.writeValue(value); } + private addSuggestionToMap(locationId: string) { + if (this.herePlatform) { + const geocodingParameters = { + locationId : locationId + }; + + this.hereGeocoder.geocode( + geocodingParameters, + (result) => { + let marker; + const location = result.Response.View[0].Result; + + marker = new H.map.Marker({ + lat : location[0].Location.DisplayPosition.Latitude, + lng : location[0].Location.DisplayPosition.Longitude + }); + + marker.setData(location[0].Location.Address.Label); + + this.hereGroup.addObject(marker); + + this.hereMap.setViewBounds(this.hereGroup.getBounds()); + }, + () => { }); + } + } + + private loadScripts() { + // You can load multiple scripts by just providing the key as argument into load method of the service + this.dynamicScriptLoader + .load('here-maps-core', 'here-maps-css') + .then(() => { + this.dynamicScriptLoader.load('here-maps-service', 'here-maps-ui', 'here-maps-events').then(data => { + // Script Loaded Successfully. We should initialize all objects needed + this.initializeMap(); + }); + }); + } + + private initializeMap() { + // Step 1: initialize communication with the platform + this.herePlatform = new H.service.Platform({ + 'app_id': HERE_MAPS_APP_ID, + 'app_code': HERE_MAPS_APP_CODE, + useCIT: false, + useHTTPS: true + }); + + this.hereGeocoder = this.herePlatform.getGeocodingService(); + this.hereGroup = new H.map.Group(); + + this.hereGroup.addEventListener('tap', (event) => { + this.hereMap.setCenter(event.target.getPosition()); + this.openBubble(event.target.getPosition(), event.target.getData()); + }, false); + + // Obtain the default map types from the platform object + const defaultLayers = this.herePlatform.createDefaultLayers(); + + // Step 2: initialize a map - this map is centered over Europe by default + // TODO: get default lang and lat from server + this.hereMap = new H.Map( + this.mapContainer.nativeElement, + defaultLayers.normal.map, + { + zoom: 3, + center: { lat: 52.5160, lng: 13.3779 } + }); + + this.hereMap.addObject(this.hereGroup); + + debugger; + // Step 3: make the map interactive + // MapEvents enables the event system + // Behavior implements default interactions for pan/zoom (also on mobile touch environments) + new H.mapevents.Behavior(new H.mapevents.MapEvents(this.hereMap)); + + // Create the default UI components + this.hereUI = H.ui.UI.createDefault(this.hereMap, defaultLayers); + } + + /** + * Removes all H.map.Marker points from the map and adds closes the info bubble + */ + private clearOldSuggestions(){ + this.hereGroup.removeAll(); + if (this.hereBubble) { + this.hereBubble.close(); + } + } + + /** + * Function to Open/Close an infobubble on the map. + * @param {H.geo.Point} position The location on the map. + * @param {String} text The contents of the infobubble. + */ + private openBubble(position, text){ + if (!this.hereBubble) { + this.hereBubble = new H.ui.InfoBubble( + position, + {content: '' + text+ ''}); + this.hereUI.addBubble(this.hereBubble); + } else { + this.hereBubble.setPosition(position); + this.hereBubble.setContent('' + text+ ''); + this.hereBubble.open(); + } + } + + private updateSuggestions() { + this.getSuggestions(this.searchTerm).subscribe((result: any) => { + this._suggestionsSubject$.next(result.suggestions); + }); + } + private getSuggestions(queryText: string): Observable { let queryParams = new HttpParams(); queryParams = queryParams.append('app_id', HERE_MAPS_APP_ID); diff --git a/custom-fields/address-custom-field/script-service.ts b/custom-fields/address-custom-field/script-service.ts new file mode 100644 index 00000000..08795f5b --- /dev/null +++ b/custom-fields/address-custom-field/script-service.ts @@ -0,0 +1,77 @@ +import { Injectable } from '@angular/core'; + +interface Scripts { + name: string; + src: string; + type: string; + element: string; +} + +export const ScriptStore: Scripts[] = [ + { name: 'here-maps-core', src: 'http://js.api.here.com/v3/3.0/mapsjs-core.js', element: 'script', type: 'text/javascript' }, + { name: 'here-maps-service', src: 'http://js.api.here.com/v3/3.0/mapsjs-service.js', element: 'script', type: 'text/javascript' }, + { name: 'here-maps-ui', src: 'https://js.api.here.com/v3/3.0/mapsjs-ui.js', element: 'script', type: 'text/javascript' }, + { name: 'here-maps-events', src: 'https://js.api.here.com/v3/3.0/mapsjs-mapevents.js', element: 'script', type: 'text/javascript' }, + { name: 'here-maps-css', src: 'https://js.api.here.com/v3/3.0/mapsjs-ui.css?dp-version=1549984893', element: 'link', type: 'text/css' } +]; + +declare var document: any; + +@Injectable() +export class DynamicScriptLoaderService { + + private scripts: any = {}; + + constructor() { + ScriptStore.forEach((script: any) => { + this.scripts[script.name] = { + loaded: false, + src: script.src, + element: script.element, + type: script.type + }; + }); + } + + load(...scripts: string[]) { + const promises: any[] = []; + scripts.forEach((script) => promises.push(this.loadScript(script))); + return Promise.all(promises); + } + + loadScript(name: string) { + return new Promise((resolve, reject) => { + if (!this.scripts[name].loaded) { + //load script + let script = document.createElement(this.scripts[name].element); + if (this.scripts[name].element === 'link') { + script.href = this.scripts[name].src; + script.rel = "stylesheet"; + } else { + script.src = this.scripts[name].src; + } + + script.type = this.scripts[name].type; + if (script.readyState) { //IE + script.onreadystatechange = () => { + if (script.readyState === "loaded" || script.readyState === "complete") { + script.onreadystatechange = null; + this.scripts[name].loaded = true; + resolve({script: name, loaded: true, status: 'Loaded'}); + } + }; + } else { //Others + script.onload = () => { + this.scripts[name].loaded = true; + resolve({script: name, loaded: true, status: 'Loaded'}); + }; + } + script.onerror = (error: any) => resolve({script: name, loaded: false, status: 'Loaded'}); + document.getElementsByTagName('head')[0].appendChild(script); + } else { + resolve({ script: name, loaded: true, status: 'Already Loaded' }); + } + }); + } + +} diff --git a/custom-fields/index.ts b/custom-fields/index.ts index 9ed82bc0..afdeada0 100644 --- a/custom-fields/index.ts +++ b/custom-fields/index.ts @@ -8,6 +8,8 @@ import { CUSTOM_FIELDS_PROVIDER } from "./custom-fields-provider"; import { FrameworkModule } from "progress-sitefinity-adminapp-sdk/app/api/v1"; import { AddressCustomFieldComponent } from "./address-custom-field/address-custom-field.component"; import { ADDRESS_CUSTOM_FIELDS_PROVIDER } from "./address-custom-field/address-custom-field-provider"; +import { DynamicScriptLoaderService } from "./address-custom-field/script-service"; + /** * The custom fields module. @@ -26,7 +28,8 @@ import { ADDRESS_CUSTOM_FIELDS_PROVIDER } from "./address-custom-field/address-c ], providers: [ CUSTOM_FIELDS_PROVIDER, - ADDRESS_CUSTOM_FIELDS_PROVIDER + ADDRESS_CUSTOM_FIELDS_PROVIDER, + DynamicScriptLoaderService ], // import the framework module as it holds the components that the AdminApp uses From a76e89eaac42350fbaef1929034f2640945612f4 Mon Sep 17 00:00:00 2001 From: Nikolai Novkirishki Date: Tue, 14 May 2019 10:30:47 +0300 Subject: [PATCH 20/32] Added other fields --- .../address-custom-field.component.html | 140 +++++++++++++----- .../address-custom-field.component.ts | 63 +++++--- 2 files changed, 151 insertions(+), 52 deletions(-) diff --git a/custom-fields/address-custom-field/address-custom-field.component.html b/custom-fields/address-custom-field/address-custom-field.component.html index 90c66662..7118c9a8 100644 --- a/custom-fields/address-custom-field/address-custom-field.component.html +++ b/custom-fields/address-custom-field/address-custom-field.component.html @@ -1,36 +1,108 @@ - - - - -
-
- No results found -
- - -
-
-
-
+
+
+ + Address line 1 + + + + + +
+
+ No results found +
+ + +
+
+
+
+
+
+
+
+ + Address line 2 + + + + +
+
+
+
+ + Country + + + + +
+
+
+
+ + County/State/Province + + + + +
+
+
+
+ + Town/City + + + + +
+
+
+
+ + Postcode/Zip + + + + +
+
diff --git a/custom-fields/address-custom-field/address-custom-field.component.ts b/custom-fields/address-custom-field/address-custom-field.component.ts index e8dc69b3..242f4768 100644 --- a/custom-fields/address-custom-field/address-custom-field.component.ts +++ b/custom-fields/address-custom-field/address-custom-field.component.ts @@ -21,8 +21,13 @@ declare var H: any; templateUrl: "./address-custom-field.component.html" }) export class AddressCustomFieldComponent extends FieldBase implements OnInit { - @ViewChild("streetAddress") streetAddress: any; + @ViewChild("streetAddress") streetAddress: ElementRef; @ViewChild("mapContainer") mapContainer: ElementRef; + @ViewChild("streetAddress2") streetAddress2: ElementRef; + @ViewChild("country") country: ElementRef; + @ViewChild("county") county: ElementRef; + @ViewChild("city") city: ElementRef; + @ViewChild("postcode") postcode: ElementRef; popupTreeConfig: any; searchTerm: string; @@ -71,7 +76,7 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { this.isPopupVisible = false; } - onNewInputValue(event: Event): void { + onNewInputValue(data: any): void { this.clearOldSuggestions(); this.updateSuggestions(); @@ -80,6 +85,19 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { this.isPopupVisible = true; } }, 300); + + const currentValue = this.getValue(); + if (currentValue) { + const addrData: AddressData = JSON.parse(currentValue); + if (data.address) { + addrData.address = { ...addrData.address, ...data.address } + this.writeValue(JSON.stringify(addrData)); + } else { + this.writeValue(JSON.stringify({ ...addrData, ...data })); + } + } else { + this.writeValue(JSON.stringify(data)); + } } onNewItemSelected(event) { @@ -87,16 +105,19 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { } writeValue(value) { - if (value && this.isJSON(value)) { - const realValue = JSON.parse(value); + if (value) { + const addrData: AddressData = JSON.parse(value); - if (realValue.label) { - this.streetAddress.nativeElement.value = realValue.label; - this.searchTerm = realValue.label; - } + this.streetAddress.nativeElement.value = addrData.label ? addrData.label : null; + this.searchTerm = this.streetAddress.nativeElement.value; + this.streetAddress2.nativeElement.value = addrData.label2 ? addrData.label2 : null; + this.country.nativeElement.value = addrData.address.country ? addrData.address.country : null; + this.county.nativeElement.value = addrData.address.county ? addrData.address.county : null; + this.city.nativeElement.value = addrData.address.city ? addrData.address.city : null; + this.postcode.nativeElement.value = addrData.address.postalCode ? addrData.address.postalCode : null; // TODO: return lat & long and add them to object - this.addSuggestionToMap(realValue.locationId); + this.addSuggestionToMap(addrData.locationId); } super.writeValue(value); @@ -173,7 +194,6 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { this.hereMap.addObject(this.hereGroup); - debugger; // Step 3: make the map interactive // MapEvents enables the event system // Behavior implements default interactions for pan/zoom (also on mobile touch environments) @@ -226,13 +246,20 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { const url = HOST + PATH; return this.http.get(url, { params: queryParams }); } +} - private isJSON(value: string) { - try { - JSON.parse(value); - } catch (e) { - return false; - } - return true; - } +interface AddressData { + locationId: string, + address: Address, + lat: string, + lng: string, + label: string, + label2: string +} + +interface Address { + country: string, + county: string, + city: string, + postalCode: string } From 24ac8e4ae0bf87451f1d663907c8f3ee4bbe3432 Mon Sep 17 00:00:00 2001 From: Nikolai Novkirishki Date: Tue, 14 May 2019 10:34:20 +0300 Subject: [PATCH 21/32] fix --- .../address-custom-field.component.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/custom-fields/address-custom-field/address-custom-field.component.ts b/custom-fields/address-custom-field/address-custom-field.component.ts index 242f4768..f87ecdea 100644 --- a/custom-fields/address-custom-field/address-custom-field.component.ts +++ b/custom-fields/address-custom-field/address-custom-field.component.ts @@ -77,6 +77,7 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { } onNewInputValue(data: any): void { + debugger this.clearOldSuggestions(); this.updateSuggestions(); @@ -111,10 +112,10 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { this.streetAddress.nativeElement.value = addrData.label ? addrData.label : null; this.searchTerm = this.streetAddress.nativeElement.value; this.streetAddress2.nativeElement.value = addrData.label2 ? addrData.label2 : null; - this.country.nativeElement.value = addrData.address.country ? addrData.address.country : null; - this.county.nativeElement.value = addrData.address.county ? addrData.address.county : null; - this.city.nativeElement.value = addrData.address.city ? addrData.address.city : null; - this.postcode.nativeElement.value = addrData.address.postalCode ? addrData.address.postalCode : null; + this.country.nativeElement.value = addrData.address && addrData.address.country ? addrData.address.country : null; + this.county.nativeElement.value = addrData.address && addrData.address.county ? addrData.address.county : null; + this.city.nativeElement.value = addrData.address && addrData.address.city ? addrData.address.city : null; + this.postcode.nativeElement.value = addrData.address && addrData.address.postalCode ? addrData.address.postalCode : null; // TODO: return lat & long and add them to object this.addSuggestionToMap(addrData.locationId); From c934202db5bc637c3f1642c1bd2673eff7cb5f3d Mon Sep 17 00:00:00 2001 From: Nikolai Novkirishki Date: Tue, 14 May 2019 11:36:19 +0300 Subject: [PATCH 22/32] Add keyboard controls --- .../address-custom-field-provider.ts | 5 ++- .../address-custom-field.component.html | 1 - .../address-custom-field.component.ts | 33 +++++++++++++------ 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/custom-fields/address-custom-field/address-custom-field-provider.ts b/custom-fields/address-custom-field/address-custom-field-provider.ts index c278821a..6e619bff 100644 --- a/custom-fields/address-custom-field/address-custom-field-provider.ts +++ b/custom-fields/address-custom-field/address-custom-field-provider.ts @@ -35,8 +35,7 @@ export class AddressCustomFieldProvider implements FieldsProvider { private findRegistration(fieldRegistryKey: FieldData): FieldRegistration { for (const pair of this.customFieldsMappings) { if (fieldRegistryKey.fieldName === pair.key.fieldName && - fieldRegistryKey.fieldType === pair.key.fieldType && - fieldRegistryKey.typeName === pair.key.typeName) { + fieldRegistryKey.fieldType === pair.key.fieldType) { return pair.registration; } } @@ -55,7 +54,7 @@ export class AddressCustomFieldProvider implements FieldsProvider { const customInputKey: FieldData = { fieldName: "CustomAddress", fieldType: "sf-text-area", - typeName: "tests" + typeName: undefined }; // The result field registration that will be returned to the AdminApp. diff --git a/custom-fields/address-custom-field/address-custom-field.component.html b/custom-fields/address-custom-field/address-custom-field.component.html index 7118c9a8..043f5a1a 100644 --- a/custom-fields/address-custom-field/address-custom-field.component.html +++ b/custom-fields/address-custom-field/address-custom-field.component.html @@ -4,7 +4,6 @@ Address line 1 { - if (!this.isPopupVisible) { + if (!this.isPopupVisible && data.label) { this.isPopupVisible = true; } }, 300); @@ -102,6 +96,7 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { } onNewItemSelected(event) { + this.isPopupVisible = false; this.writeValue(JSON.stringify(event.data)); } @@ -124,6 +119,23 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { super.writeValue(value); } + onEscapeKey(): void { + this.isPopupVisible = false; + } + + onFocusNextNode(): void { + this.popupTree.focusNextNode(); + } + + onFocusPreviousNode(): void { + this.popupTree.focusPreviousNode(); + } + + onEnterKey(): void { + this.isPopupVisible = false; + this.popupTree.selectCurrentNode(); + } + private addSuggestionToMap(locationId: string) { if (this.herePlatform) { const geocodingParameters = { @@ -146,6 +158,7 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { this.hereGroup.addObject(marker); this.hereMap.setViewBounds(this.hereGroup.getBounds()); + this.hereMap.setZoom(16); }, () => { }); } @@ -207,7 +220,7 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { /** * Removes all H.map.Marker points from the map and adds closes the info bubble */ - private clearOldSuggestions(){ + private clearOldSuggestions() { this.hereGroup.removeAll(); if (this.hereBubble) { this.hereBubble.close(); From ad4028d46ef1832dad101a3f70b150f83438299e Mon Sep 17 00:00:00 2001 From: Nikolai Novkirishki Date: Tue, 14 May 2019 11:42:33 +0300 Subject: [PATCH 23/32] Fix initial location on map --- .../address-custom-field.component.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/custom-fields/address-custom-field/address-custom-field.component.ts b/custom-fields/address-custom-field/address-custom-field.component.ts index f257d2c0..2b2f6f7c 100644 --- a/custom-fields/address-custom-field/address-custom-field.component.ts +++ b/custom-fields/address-custom-field/address-custom-field.component.ts @@ -113,7 +113,7 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { this.postcode.nativeElement.value = addrData.address && addrData.address.postalCode ? addrData.address.postalCode : null; // TODO: return lat & long and add them to object - this.addSuggestionToMap(addrData.locationId); + this.addSuggestionToMap(addrData); } super.writeValue(value); @@ -136,10 +136,10 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { this.popupTree.selectCurrentNode(); } - private addSuggestionToMap(locationId: string) { - if (this.herePlatform) { + private addSuggestionToMap(addressData: AddressData) { + if (this.herePlatform && addressData && addressData.locationId) { const geocodingParameters = { - locationId : locationId + locationId : addressData.locationId }; this.hereGeocoder.geocode( @@ -215,6 +215,10 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { // Create the default UI components this.hereUI = H.ui.UI.createDefault(this.hereMap, defaultLayers); + + // add the suggestion to map if such + const currenValue = this.getValue(); + this.addSuggestionToMap(currenValue ? JSON.parse(currenValue) : null); } /** From 187d62074972476b7f9873e7f60fb7be19559857 Mon Sep 17 00:00:00 2001 From: Nikolai Novkirishki Date: Tue, 14 May 2019 11:51:02 +0300 Subject: [PATCH 24/32] fix lint --- .../address-custom-field.component.ts | 61 ++++++++++--------- .../address-custom-field/script-service.ts | 30 ++++----- custom-fields/index.ts | 1 - 3 files changed, 47 insertions(+), 45 deletions(-) diff --git a/custom-fields/address-custom-field/address-custom-field.component.ts b/custom-fields/address-custom-field/address-custom-field.component.ts index 2b2f6f7c..a61f31d6 100644 --- a/custom-fields/address-custom-field/address-custom-field.component.ts +++ b/custom-fields/address-custom-field/address-custom-field.component.ts @@ -9,11 +9,11 @@ import { DynamicScriptLoaderService } from "./script-service"; * NOTE: Replace this example keys with your subscription keys. * For more information on how to get a key check here: https://developer.here.com/?create=Freemium-Basic&keepState=true&step=terms */ -const HERE_MAPS_APP_ID = 'iV0wv8ievlwH8Fd5Raii'; -const HERE_MAPS_APP_CODE = 'AHNPEuMJkSuNjkP7SpW2xg'; +const HERE_MAPS_APP_ID = "iV0wv8ievlwH8Fd5Raii"; +const HERE_MAPS_APP_CODE = "AHNPEuMJkSuNjkP7SpW2xg"; -const HOST = 'http://autocomplete.geocoder.api.here.com'; -const PATH = '/6.2/suggest.json'; +const HOST = "http://autocomplete.geocoder.api.here.com"; +const PATH = "/6.2/suggest.json"; declare var H: any; @@ -34,6 +34,7 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { searchTerm: string; hasSuggestions: Observable; isPopupVisible: boolean = false; + hereBahavior: any; private _suggestionsSubject$: BehaviorSubject; private _suggestions$: Observable; @@ -50,7 +51,7 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { this._suggestionsSubject$ = new BehaviorSubject([]); this._suggestions$ = this._suggestionsSubject$.asObservable(); - this.hasSuggestions = this._suggestions$.pipe(map((arr) => { return arr.length > 0;})); + this.hasSuggestions = this._suggestions$.pipe(map((arr) => { return arr.length > 0; })); } get suggestions$(): Observable { @@ -85,7 +86,7 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { if (currentValue) { const addrData: AddressData = JSON.parse(currentValue); if (data.address) { - addrData.address = { ...addrData.address, ...data.address } + addrData.address = { ...addrData.address, ...data.address }; this.writeValue(JSON.stringify(addrData)); } else { this.writeValue(JSON.stringify({ ...addrData, ...data })); @@ -160,16 +161,18 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { this.hereMap.setViewBounds(this.hereGroup.getBounds()); this.hereMap.setZoom(16); }, - () => { }); + () => { + // empty + }); } } private loadScripts() { // You can load multiple scripts by just providing the key as argument into load method of the service this.dynamicScriptLoader - .load('here-maps-core', 'here-maps-css') + .load("here-maps-core", "here-maps-css") .then(() => { - this.dynamicScriptLoader.load('here-maps-service', 'here-maps-ui', 'here-maps-events').then(data => { + this.dynamicScriptLoader.load("here-maps-service", "here-maps-ui", "here-maps-events").then(data => { // Script Loaded Successfully. We should initialize all objects needed this.initializeMap(); }); @@ -179,8 +182,8 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { private initializeMap() { // Step 1: initialize communication with the platform this.herePlatform = new H.service.Platform({ - 'app_id': HERE_MAPS_APP_ID, - 'app_code': HERE_MAPS_APP_CODE, + "app_id": HERE_MAPS_APP_ID, + "app_code": HERE_MAPS_APP_CODE, useCIT: false, useHTTPS: true }); @@ -188,7 +191,7 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { this.hereGeocoder = this.herePlatform.getGeocodingService(); this.hereGroup = new H.map.Group(); - this.hereGroup.addEventListener('tap', (event) => { + this.hereGroup.addEventListener("tap", (event) => { this.hereMap.setCenter(event.target.getPosition()); this.openBubble(event.target.getPosition(), event.target.getData()); }, false); @@ -211,7 +214,7 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { // Step 3: make the map interactive // MapEvents enables the event system // Behavior implements default interactions for pan/zoom (also on mobile touch environments) - new H.mapevents.Behavior(new H.mapevents.MapEvents(this.hereMap)); + this.hereBahavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(this.hereMap)); // Create the default UI components this.hereUI = H.ui.UI.createDefault(this.hereMap, defaultLayers); @@ -236,15 +239,15 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { * @param {H.geo.Point} position The location on the map. * @param {String} text The contents of the infobubble. */ - private openBubble(position, text){ + private openBubble(position, text) { if (!this.hereBubble) { this.hereBubble = new H.ui.InfoBubble( position, - {content: '' + text+ ''}); + {content: "" + text + ""}); this.hereUI.addBubble(this.hereBubble); } else { this.hereBubble.setPosition(position); - this.hereBubble.setContent('' + text+ ''); + this.hereBubble.setContent("" + text + ""); this.hereBubble.open(); } } @@ -257,9 +260,9 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { private getSuggestions(queryText: string): Observable { let queryParams = new HttpParams(); - queryParams = queryParams.append('app_id', HERE_MAPS_APP_ID); - queryParams = queryParams.append('app_code', HERE_MAPS_APP_CODE); - queryParams = queryParams.append('query', queryText); + queryParams = queryParams.append("app_id", HERE_MAPS_APP_ID); + queryParams = queryParams.append("app_code", HERE_MAPS_APP_CODE); + queryParams = queryParams.append("query", queryText); const url = HOST + PATH; return this.http.get(url, { params: queryParams }); @@ -267,17 +270,17 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { } interface AddressData { - locationId: string, - address: Address, - lat: string, - lng: string, - label: string, - label2: string + locationId: string; + address: Address; + lat: string; + lng: string; + label: string; + label2: string; } interface Address { - country: string, - county: string, - city: string, - postalCode: string + country: string; + county: string; + city: string; + postalCode: string; } diff --git a/custom-fields/address-custom-field/script-service.ts b/custom-fields/address-custom-field/script-service.ts index 08795f5b..a2d786ff 100644 --- a/custom-fields/address-custom-field/script-service.ts +++ b/custom-fields/address-custom-field/script-service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Injectable } from "@angular/core"; interface Scripts { name: string; @@ -8,11 +8,11 @@ interface Scripts { } export const ScriptStore: Scripts[] = [ - { name: 'here-maps-core', src: 'http://js.api.here.com/v3/3.0/mapsjs-core.js', element: 'script', type: 'text/javascript' }, - { name: 'here-maps-service', src: 'http://js.api.here.com/v3/3.0/mapsjs-service.js', element: 'script', type: 'text/javascript' }, - { name: 'here-maps-ui', src: 'https://js.api.here.com/v3/3.0/mapsjs-ui.js', element: 'script', type: 'text/javascript' }, - { name: 'here-maps-events', src: 'https://js.api.here.com/v3/3.0/mapsjs-mapevents.js', element: 'script', type: 'text/javascript' }, - { name: 'here-maps-css', src: 'https://js.api.here.com/v3/3.0/mapsjs-ui.css?dp-version=1549984893', element: 'link', type: 'text/css' } + { name: "here-maps-core", src: "http://js.api.here.com/v3/3.0/mapsjs-core.js", element: "script", type: "text/javascript" }, + { name: "here-maps-service", src: "http://js.api.here.com/v3/3.0/mapsjs-service.js", element: "script", type: "text/javascript" }, + { name: "here-maps-ui", src: "https://js.api.here.com/v3/3.0/mapsjs-ui.js", element: "script", type: "text/javascript" }, + { name: "here-maps-events", src: "https://js.api.here.com/v3/3.0/mapsjs-mapevents.js", element: "script", type: "text/javascript" }, + { name: "here-maps-css", src: "https://js.api.here.com/v3/3.0/mapsjs-ui.css?dp-version=1549984893", element: "link", type: "text/css" } ]; declare var document: any; @@ -42,9 +42,9 @@ export class DynamicScriptLoaderService { loadScript(name: string) { return new Promise((resolve, reject) => { if (!this.scripts[name].loaded) { - //load script + // load script let script = document.createElement(this.scripts[name].element); - if (this.scripts[name].element === 'link') { + if (this.scripts[name].element === "link") { script.href = this.scripts[name].src; script.rel = "stylesheet"; } else { @@ -52,24 +52,24 @@ export class DynamicScriptLoaderService { } script.type = this.scripts[name].type; - if (script.readyState) { //IE + if (script.readyState) { // IE script.onreadystatechange = () => { if (script.readyState === "loaded" || script.readyState === "complete") { script.onreadystatechange = null; this.scripts[name].loaded = true; - resolve({script: name, loaded: true, status: 'Loaded'}); + resolve({script: name, loaded: true, status: "Loaded"}); } }; - } else { //Others + } else { // Others script.onload = () => { this.scripts[name].loaded = true; - resolve({script: name, loaded: true, status: 'Loaded'}); + resolve({script: name, loaded: true, status: "Loaded"}); }; } - script.onerror = (error: any) => resolve({script: name, loaded: false, status: 'Loaded'}); - document.getElementsByTagName('head')[0].appendChild(script); + script.onerror = (error: any) => resolve({script: name, loaded: false, status: "Loaded"}); + document.getElementsByTagName("head")[0].appendChild(script); } else { - resolve({ script: name, loaded: true, status: 'Already Loaded' }); + resolve({ script: name, loaded: true, status: "Already Loaded" }); } }); } diff --git a/custom-fields/index.ts b/custom-fields/index.ts index afdeada0..c9eb2c3f 100644 --- a/custom-fields/index.ts +++ b/custom-fields/index.ts @@ -10,7 +10,6 @@ import { AddressCustomFieldComponent } from "./address-custom-field/address-cust import { ADDRESS_CUSTOM_FIELDS_PROVIDER } from "./address-custom-field/address-custom-field-provider"; import { DynamicScriptLoaderService } from "./address-custom-field/script-service"; - /** * The custom fields module. */ From 9bbd0cab0df3816b6fc991713ac632a73ce57ca1 Mon Sep 17 00:00:00 2001 From: Nikolai Novkirishki Date: Tue, 14 May 2019 12:00:44 +0300 Subject: [PATCH 25/32] Fix double markers bug --- .../address-custom-field/address-custom-field.component.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/custom-fields/address-custom-field/address-custom-field.component.ts b/custom-fields/address-custom-field/address-custom-field.component.ts index a61f31d6..e2c0f9fd 100644 --- a/custom-fields/address-custom-field/address-custom-field.component.ts +++ b/custom-fields/address-custom-field/address-custom-field.component.ts @@ -68,7 +68,6 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { } onFocusOut(): void { - this.isPopupVisible = false; } @@ -99,6 +98,8 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { onNewItemSelected(event) { this.isPopupVisible = false; this.writeValue(JSON.stringify(event.data)); + const currenValue = this.getValue(); + this.addSuggestionToMap(currenValue ? JSON.parse(currenValue) : null); } writeValue(value) { @@ -112,9 +113,6 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { this.county.nativeElement.value = addrData.address && addrData.address.county ? addrData.address.county : null; this.city.nativeElement.value = addrData.address && addrData.address.city ? addrData.address.city : null; this.postcode.nativeElement.value = addrData.address && addrData.address.postalCode ? addrData.address.postalCode : null; - - // TODO: return lat & long and add them to object - this.addSuggestionToMap(addrData); } super.writeValue(value); From 5aa6948465252e0b1335e2310aacde958ca6e482 Mon Sep 17 00:00:00 2001 From: Nikolai Novkirishki Date: Tue, 14 May 2019 12:40:15 +0300 Subject: [PATCH 26/32] Added reverse geocoding --- .../address-custom-field.component.ts | 83 +++++++++++++++---- 1 file changed, 69 insertions(+), 14 deletions(-) diff --git a/custom-fields/address-custom-field/address-custom-field.component.ts b/custom-fields/address-custom-field/address-custom-field.component.ts index e2c0f9fd..b3921d77 100644 --- a/custom-fields/address-custom-field/address-custom-field.component.ts +++ b/custom-fields/address-custom-field/address-custom-field.component.ts @@ -135,6 +135,56 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { this.popupTree.selectCurrentNode(); } + /** + * An event listener is added to listen to tap events on the map. + * Clicking on the map displays an alert box containing the latitude and longitude + * of the location pressed. + * @param {H.Map} map A HERE Map instance within the application + */ + private setUpClickListener(hereMap) { + // Attach an event listener to map display + // obtain the coordinates and display in an alert box. + hereMap.addEventListener("tap", (event) => { + const coord = hereMap.screenToGeo(event.currentPointer.viewportX, event.currentPointer.viewportY); + this.clearOldSuggestions(); + + // get address suggestion// Create the parameters for the reverse geocoding request: + const reverseGeocodingParameters = { + prox: `${coord.lat},${coord.lng}`, + mode: "retrieveAddresses", + maxresults: 1 + }; + + // Call the geocode method with the geocoding parameters, + // the callback and an error callback function (called if a + // communication error occurs): + this.hereGeocoder.reverseGeocode( + reverseGeocodingParameters, + (data) => { + const location = data.Response.View[0].Result[0].Location; + const addrData: AddressData = { + label: location.Address.Label, + lat: location.DisplayPosition.Latitude, + lng: location.DisplayPosition.Longitude, + label2: null, + locationId: location.LocationId, + address: { + country: location.Address.Country, + county: location.Address.County, + city: location.Address.City, + postalCode: location.Address.PostalCode + } + }; + + this.addMarkerToMap(coord.lat, coord.lng, location.Address.Label); + this.writeValue(JSON.stringify(addrData)); + }, + () => { + // empty + }); + }); + } + private addSuggestionToMap(addressData: AddressData) { if (this.herePlatform && addressData && addressData.locationId) { const geocodingParameters = { @@ -144,20 +194,11 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { this.hereGeocoder.geocode( geocodingParameters, (result) => { - let marker; const location = result.Response.View[0].Result; - - marker = new H.map.Marker({ - lat : location[0].Location.DisplayPosition.Latitude, - lng : location[0].Location.DisplayPosition.Longitude - }); - - marker.setData(location[0].Location.Address.Label); - - this.hereGroup.addObject(marker); - - this.hereMap.setViewBounds(this.hereGroup.getBounds()); - this.hereMap.setZoom(16); + this.addMarkerToMap( + location[0].Location.DisplayPosition.Latitude, + location[0].Location.DisplayPosition.Longitude, + location[0].Location.Address.Label); }, () => { // empty @@ -165,6 +206,19 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { } } + private addMarkerToMap(lat: string, lng: string, label: string) { + let marker; + + marker = new H.map.Marker({ lat, lng }); + + marker.setData(label); + + this.hereGroup.addObject(marker); + + this.hereMap.setViewBounds(this.hereGroup.getBounds()); + this.hereMap.setZoom(16); + } + private loadScripts() { // You can load multiple scripts by just providing the key as argument into load method of the service this.dynamicScriptLoader @@ -189,7 +243,7 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { this.hereGeocoder = this.herePlatform.getGeocodingService(); this.hereGroup = new H.map.Group(); - this.hereGroup.addEventListener("tap", (event) => { + this.hereGroup.addEventListener("longpress", (event) => { this.hereMap.setCenter(event.target.getPosition()); this.openBubble(event.target.getPosition(), event.target.getData()); }, false); @@ -208,6 +262,7 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { }); this.hereMap.addObject(this.hereGroup); + this.setUpClickListener(this.hereMap); // Step 3: make the map interactive // MapEvents enables the event system From 02450871b4b663d4ae6b569cad4a4abb2a05fde0 Mon Sep 17 00:00:00 2001 From: Elena Ganeva Date: Tue, 14 May 2019 12:59:06 +0300 Subject: [PATCH 27/32] Changed field name --- .../address-custom-field/address-custom-field-provider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom-fields/address-custom-field/address-custom-field-provider.ts b/custom-fields/address-custom-field/address-custom-field-provider.ts index c278821a..5162ee35 100644 --- a/custom-fields/address-custom-field/address-custom-field-provider.ts +++ b/custom-fields/address-custom-field/address-custom-field-provider.ts @@ -53,7 +53,7 @@ export class AddressCustomFieldProvider implements FieldsProvider { // The typename is the OData entity set name. It matches the url segment when navigating // to the list view of the specific type. const customInputKey: FieldData = { - fieldName: "CustomAddress", + fieldName: "HereAddress", fieldType: "sf-text-area", typeName: "tests" }; From 1e0c59c00e817297f1f76d6cde74c130ef0b50a5 Mon Sep 17 00:00:00 2001 From: Nikolai Novkirishki Date: Tue, 14 May 2019 13:39:08 +0300 Subject: [PATCH 28/32] Remove first field title --- .../address-custom-field/address-custom-field.component.html | 5 ----- 1 file changed, 5 deletions(-) diff --git a/custom-fields/address-custom-field/address-custom-field.component.html b/custom-fields/address-custom-field/address-custom-field.component.html index 043f5a1a..b62a35c8 100644 --- a/custom-fields/address-custom-field/address-custom-field.component.html +++ b/custom-fields/address-custom-field/address-custom-field.component.html @@ -1,8 +1,4 @@
-
- - Address line 1 - @@ -36,7 +32,6 @@ -
From dab61d23ae354615918370d14762681e6030bd2d Mon Sep 17 00:00:00 2001 From: Nikolai Novkirishki Date: Tue, 14 May 2019 18:03:23 +0300 Subject: [PATCH 29/32] Config service --- .../address-custom-field.component.ts | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/custom-fields/address-custom-field/address-custom-field.component.ts b/custom-fields/address-custom-field/address-custom-field.component.ts index b3921d77..2897e01b 100644 --- a/custom-fields/address-custom-field/address-custom-field.component.ts +++ b/custom-fields/address-custom-field/address-custom-field.component.ts @@ -1,17 +1,10 @@ import { Component, OnInit, ViewChild, ElementRef } from "@angular/core"; -import { FieldBase } from "progress-sitefinity-adminapp-sdk/app/api/v1"; +import { FieldBase, HTTP_PREFIX } from "progress-sitefinity-adminapp-sdk/app/api/v1"; import { HttpClient, HttpParams } from "@angular/common/http"; -import { Observable, BehaviorSubject } from "rxjs"; +import { Observable, BehaviorSubject, ReplaySubject } from "rxjs"; import { map } from "rxjs/operators"; import { DynamicScriptLoaderService } from "./script-service"; -/* - * NOTE: Replace this example keys with your subscription keys. - * For more information on how to get a key check here: https://developer.here.com/?create=Freemium-Basic&keepState=true&step=terms - */ -const HERE_MAPS_APP_ID = "iV0wv8ievlwH8Fd5Raii"; -const HERE_MAPS_APP_CODE = "AHNPEuMJkSuNjkP7SpW2xg"; - const HOST = "http://autocomplete.geocoder.api.here.com"; const PATH = "/6.2/suggest.json"; @@ -45,6 +38,7 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { private hereGroup: any; private hereUI: any; private hereBubble: any; + private credentials: HereCredentials; constructor(private http: HttpClient, private dynamicScriptLoader: DynamicScriptLoaderService) { super(); @@ -64,7 +58,9 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { }; this.searchTerm = this.getValue(); - this.loadScripts(); + this.getCredentials().subscribe(() => { + this.loadScripts(); + }); } onFocusOut(): void { @@ -135,6 +131,17 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { this.popupTree.selectCurrentNode(); } + private getCredentials(): Observable { + const result = new ReplaySubject(1); + const url = `${HTTP_PREFIX}/restapi/hereCredentials?format=json`; + this.http.get(url).subscribe((data: HereCredentials) => { + this.credentials = data; + result.next(this.credentials); + }); + + return result; + } + /** * An event listener is added to listen to tap events on the map. * Clicking on the map displays an alert box containing the latitude and longitude @@ -234,8 +241,8 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { private initializeMap() { // Step 1: initialize communication with the platform this.herePlatform = new H.service.Platform({ - "app_id": HERE_MAPS_APP_ID, - "app_code": HERE_MAPS_APP_CODE, + "app_id": this.credentials.AppId, + "app_code": this.credentials.AppCode, useCIT: false, useHTTPS: true }); @@ -313,8 +320,8 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { private getSuggestions(queryText: string): Observable { let queryParams = new HttpParams(); - queryParams = queryParams.append("app_id", HERE_MAPS_APP_ID); - queryParams = queryParams.append("app_code", HERE_MAPS_APP_CODE); + queryParams = queryParams.append("app_id", this.credentials.AppId); + queryParams = queryParams.append("app_code", this.credentials.AppCode); queryParams = queryParams.append("query", queryText); const url = HOST + PATH; @@ -337,3 +344,8 @@ interface Address { city: string; postalCode: string; } + +interface HereCredentials { + AppId: string, + AppCode: string +} From e3502056e13c0755a4089dab4151e5491288b393 Mon Sep 17 00:00:00 2001 From: Nikolai Novkirishki Date: Tue, 14 May 2019 18:19:01 +0300 Subject: [PATCH 30/32] Fix clearing suggestions --- .../address-custom-field/address-custom-field.component.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/custom-fields/address-custom-field/address-custom-field.component.ts b/custom-fields/address-custom-field/address-custom-field.component.ts index 2897e01b..f7b1f4d0 100644 --- a/custom-fields/address-custom-field/address-custom-field.component.ts +++ b/custom-fields/address-custom-field/address-custom-field.component.ts @@ -68,7 +68,6 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { } onNewInputValue(data: any): void { - this.clearOldSuggestions(); this.updateSuggestions(); setTimeout(() => { @@ -153,7 +152,6 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { // obtain the coordinates and display in an alert box. hereMap.addEventListener("tap", (event) => { const coord = hereMap.screenToGeo(event.currentPointer.viewportX, event.currentPointer.viewportY); - this.clearOldSuggestions(); // get address suggestion// Create the parameters for the reverse geocoding request: const reverseGeocodingParameters = { @@ -214,6 +212,7 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { } private addMarkerToMap(lat: string, lng: string, label: string) { + this.clearOldSuggestions(); let marker; marker = new H.map.Marker({ lat, lng }); From bbc86c2a39f77fc94671b5d39446145cc49e4375 Mon Sep 17 00:00:00 2001 From: Nikolai Novkirishki Date: Tue, 14 May 2019 18:20:23 +0300 Subject: [PATCH 31/32] Fix lint --- .../address-custom-field/address-custom-field.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom-fields/address-custom-field/address-custom-field.component.ts b/custom-fields/address-custom-field/address-custom-field.component.ts index f7b1f4d0..cf307d6b 100644 --- a/custom-fields/address-custom-field/address-custom-field.component.ts +++ b/custom-fields/address-custom-field/address-custom-field.component.ts @@ -345,6 +345,6 @@ interface Address { } interface HereCredentials { - AppId: string, - AppCode: string + AppId: string; + AppCode: string; } From 038ee91dd6a0b5ac05c5f5b7a93a8b3df71ef8a1 Mon Sep 17 00:00:00 2001 From: Nikolai Novkirishki Date: Tue, 14 May 2019 20:45:52 +0300 Subject: [PATCH 32/32] Fix --- .../address-custom-field/address-custom-field.component.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/custom-fields/address-custom-field/address-custom-field.component.ts b/custom-fields/address-custom-field/address-custom-field.component.ts index cf307d6b..cca42baa 100644 --- a/custom-fields/address-custom-field/address-custom-field.component.ts +++ b/custom-fields/address-custom-field/address-custom-field.component.ts @@ -200,6 +200,10 @@ export class AddressCustomFieldComponent extends FieldBase implements OnInit { geocodingParameters, (result) => { const location = result.Response.View[0].Result; + addressData.lat = location[0].Location.DisplayPosition.Latitude; + addressData.lng = location[0].Location.DisplayPosition.Longitude; + this.writeValue(JSON.stringify(addressData)); + this.addMarkerToMap( location[0].Location.DisplayPosition.Latitude, location[0].Location.DisplayPosition.Longitude,