Multiple values for the 'scope' property in a manifest.json file. - progressive-web-apps

I am working on an existing application with legacy code base. While I would love to see the entire app converted to a PWA some day, for now my plan is one page (one url) at a time. For this, I know that the "scope" property is going to be my best friend for some time. While I can pass "." as a value to the property and treat all the routes as PWS, but as I mentioned earlier, that's not the plan. Hence, below is not an option for me.
{
"scope" : "."
}
For now, I plan on covering only two routes under the PWA scope, "list page" and the "details page". Hence I would have preferred something like below to work, but it did not.
{
"scope" : [
"/list",
"/id/details"
]
}
Any suggestion(s)?

The scope member is a string that represents the navigation scope of this web application's application context.
https://w3c.github.io/manifest/#scope-member
It will not support an array of multiple values.
An option would be to use the scope /pwa/ (or similar) and as you migrate sections of the app redirect /list to /pwa/list, etc.

Related

How can I create and update pages dynamically in Sulu CMS?

I have the following situation:
A database stores information about houses (address, number of rooms, date built, last selling price, etc.)
This database is being manipulated through an app (let's call that app the "backend house app") that cannot be directly integrated in a Sulu-driven app. I can access the stored data through an API that gives me JSON-representations of House-objects. I can also have the app launch some sort of call to a Sulu-driven app when a house is created, updated or deleted.
The Sulu-driven app (let's call that the "frontend house app") with templates for "house", "room", etc., is connected to a different database on a different server. This Sulu-driven app's website-environment shows house-pages with room-pages where some content is pre-filled through a connection to the "backend house app". Other content only exists on the database of the "frontend house app", like user comments, appraisals of interior design, etc., according to configured aspects of the Sulu-templates.
What I want to achieve, is a way to automate the creation, updating and deletion of "frontend house app"-pages based on activity in the "backend house app".
For instance, when a new house is added in the "backend house app", I want it to notify the "frontend house app" so that the "frontend house app" will automatically create the entire node-tree for the newly added house. Meaning: a "house"-page with the required data filled in, "room"-pages for each room, etc., so that the content manager of the "frontend house app" can see the entire tree of the newly added house in the workspace and can start manipulating content in the already available templates. In addition to automatically creating these pages, I also want to pre-set the rights to update and create, since the content manager of the "frontend house app" must not be able to create new rooms or change the name of the house, for instance.
I did not manage to get it working, I'll just add what I already done to show where I got stuck.
I started out with the following code, in a controller that extends Sulu's own WebsiteController:
$documentManager = $this->get('sulu_document_manager.document_manager');
$nodeManager = $this->get('sulu_document_manager.node_manager');
$parentHousesDocument = $documentManager->find('/cmf/immo/routes/nl/huizen', 'nl');
$newHouseDocument = $documentManager->create('page');
// The backendApi just gives a House object with data from the backend
// In this case we get an existing House with id 1
$house = $backendApi->getHouseWithId(1);
$newHouseDocument->setTitle($house->getName()); // For instance 'Smurfhouse'
$newHouseDocument->setLocale('nl'); // Nl is the only locale we have
$newHouseDocument->setParent($parentHouseDocument); // A default page where all the houses are listed
$newHouseDocument->setStructureType('house'); // Since we have a house.xml template
// I need to grab the structure to fill it with values from the House object
$structure = $newHouseDocument->getStructure();
$structure->bind([
'title' => $house->getName(),
'houseId' => $house->getId(),
]);
$newHouseDocument->setWorkflowStage(WorkflowStage::PUBLISHED); // You would expect this to automatically publish the document, but apparently it doesn't... I took it from a test I reverse-engineered in trying to create a page, I have no clue what it is supposed to change.
$nodeManager->createPath('/cmf/immo/routes/nl/huizen/' . $house->getId());
$documentManager->persist(
$newHouseDocument,
'nl',
[
'path' => '/cmf/immo/contents/huizen/' . Slugifier::slugify($house->getName()), // Assume for argument's sake that the Slugifier just slugifies the name...
'auto_create' => true, // Took this value from a test that creates pages, don't know whether it is necessary
'load_ghost_content' => false, // Idem
]
);
$documentManager->flush();
Now, when I fire the controller action, I first get the exception
Property "url" in structure "house" is required but no value was given.
I tried to fix this by just manually binding the property 'url' with value '/huizen/' . $house->getId() to $structure, at the point where I bind the other values. But this doesn't fix it, as apparently the url value is overwritten somewhere in the persist event chain, and I haven't yet found where.
However, I can, just for testing purposes, manually override the url in the StructureSubscriber that handles the mapping for this particular persist event. If I do this, something gets created in the Sulu-app-database - hurray!
My phpcr_nodes table lists two extra records, one for the RouteDocument referring to /cmf/immo/routes/nl/huizen/1, and one for the PageDocument referring to /cmf/immo/contents/huizen/smurfhouse. Both have the workspace_name column filled with the value default_live. However, as long as there are not also records that are complete duplicates of these two records except with the value default in the workspace_name column, the pages will not appear in the Sulu admin CMS environment. Needless to say, they will also not appear on the public website proper.
Furthermore, when I let the DocumentManager in my controller action try to ->find my newly created document, I get a document of the class UnknownDocument. Hence, I cannot have the DocumentManager go ->publish on it; an Exception ensues. If I visit the pages in the Sulu admin environment, they are hence unpublished; once I publish them there, they can be found by the DocumentManager in the controller action - even if I later unpublish them. They are no longer UnknownDocument, for some reason. However, even if they can be found, I cannot have the DocumentManager go ->unpublish nor ->publish - that just has NO effect on the actual documents.
I was hoping there would be a Sulu cookbook-recipe or another piece of documentation that extensively describes how to create fully published pages dynamically, thus without going through the 'manual labor' of the actual CMS environment, but so far I haven't found one... All help is much appreciated :)
PS: For the purposes of being complete: we're running Sulu on a Windows server environment on PHP 7.1; dbase is PostgreSQL, Sulu being a local forked version of release tag 1.4.7 because I had to make some changes to the way Sulu handles uploaded files to get it to work on a Windows environment.
EDIT: a partial solution for making a new house page if none exists already (not explicitly using the AdminKernel, but should of course be run in a context where the AdminKernel is active):
public function getOrCreateHuisPagina(Huis $huis)
{
$parent = $this->documentManager->find('/cmf/immo/routes/nl/huizen', 'nl'); // This is indeed the route document for the "collector page" of all the houses, but this doesn't seem to give any problems (see below)
try {
$document = $this->documentManager->find('/cmf/immo/routes/nl/huizen/' . $huis->id(), 'nl'); // Here I'm checking whether the page already exists
} catch(DocumentNotFoundException $e) {
$document = $this->setupPublishedPage();
$document->setTitle($huis->naam());
$document->setStructureType('huis_detail');
$document->setResourceSegment('/huizen');
$document->setParent($parent);
$document->getStructure()->bind([
'title' => $huis->naam(), // Not sure if this is required seeing as I already set the title
'huis_id' => $huis->id(),
]);
$this->documentManager->persist(
$document,
'nl',
[
'parent_path' => '/cmf/immo/contents/huizen', // Explicit path to the content document of the parnt
]
);
}
$this->documentManager->publish($document, 'nl');
return $document;
}
First of all I think the following line does not load what you want it to load:
$parentHousesDocument = $documentManager->find('/cmf/immo/routes/nl/huizen', 'nl');
It loads the route instead of the page document, so it should look like the following:
$parentHousesDocument = $documentManager->find('/cmf/immo/contents/nl/huizen', 'nl');
Regarding your error with the URL, instead of overriding the StructureSubscriber you should simple use the setResourceSegment method of the document, which does exactly what you need :-)
And the default_live workspace is wrong, is it possible that you are running these commands on the website kernel? The thing is that the WebsiteKernel has the default_live workspace as default, and therefore writes the content in this workspace. If you run the command with the AdminKernel it should land in the default workspace, and you should be able to copy it into the default_live workspace with the publish method of the DocumentManager.
I hope that helps :-)

How to change FLP - Parameter within the same app

I have an app with two tiles on the Fiori Launchpad, setting a URL parameter to differ. I result in having following two Hashes :
#SemanticObject-action?myparam=tile1&/inner/app/path
#SemanticObject-action?myparam=tile2&/inner/app/path
Now I want to navigate from one tile to the other.
In the Component.js I get that parameter:
this.getComponentData().startupParameters.myparam[0];
and so I can decide how it has to act.
Now how to change that param at a controller? I tried (what did not work):
//simply overwriting does not work
this.getComponentData().startupParameters.myparam[0] = "tile2";
sap.ushell.Container.getService("CrossApplicationNavigation").toExternal(
{params : {myparam: "tile2"}});
What I do not want is using things like window.location, regex for replacement of "tile1" with "tile2" or such inproper solutions.
window.location.hash = window.location.hash.replace(sOldParam, sNewParam);

Fiori -- Cross application navigation; Handling startup parameters?

This question is related to:
Fiori - Cross Application Navigation
http://help.sap.com/saphelp_uiaddon10/helpdata/en/07/9561b716bb4f2f8ae4e47bacbdb86d/content.htm
Remove URL params on routing
My use case is like this:
I have multiple applications that should link to others (deep).
Since documentation of cross navigation mention to avoid deep links I decided to use a startup parameter.
For example:
Application A has a list of some items in the detail view of one item there is a reference to another application B that contains some other details.
Assume A shows article details and B shows some details of a producer of an article.
Application A would now use some navigation like this:
sap.ushell.Container.getService("CrossApplicationNavigation").hrefForExternal({
target : { semanticObject : "ApplicationB", action : "display" },
params : { "someID" : "102343333"}
})
Now in application B I use code like this inside the Component.js at the end of the init method.
var oRouter = that.getRouter().initialize();
var oComponentData = this.getComponentData();
if (oComponentData.startupParameters) {
oRouter.navTo("SomeView", {
someId : oComponentData.startupParameters.someID[0],
}, false);
}
First question: Is this the right place for handling the startup parameters?
Second question: If I using the navigation the startup parameter will still be in the code, I would prefer to remove it, but how?
Update
In the target application (B) it would lead to the following URL:
https://server/sap/bc/ui5_ui5/ui2/ushell/shells/abap/FioriLaunchpad.html?sap-client=100&sap-language=EN#SemObject-display?someID=102343333&/SomeView(102343333)/
Anyhow I would prefere to have something like this:
https://server/sap/bc/ui5_ui5/ui2/ushell/shells/abap/FioriLaunchpad.html?sap-client=100&sap-language=EN#SemObject-display?/SomeView(102343333)/
The parameter must be retrieved as
var oComponentData = this.getComponentData();
if (oComponentData.startupParameters) {
oRouter.navTo("SomeView", {
someId : oComponentData.startupParameters.someID[0],
}, false);
as you write. In Fiori applications, the startup parameters injected into the Component data of your constructor may have been renamed, enriched by further default values etc.. Thus they may be distinct from the parameter one observes in the url. Applications are advised to refrain from trying to inspect the URL directly.
If one supplies a very long set of url parameters, one will observer that the FLP replaces some of them with sap-intent-param=AS123424 ("compacted URL") to work around url length restrictions on some platforms and in bookmarks, in the
getComponentData().startupParameters one will receive the full set of parameters).
As to the second question.
No, there is currently no way to "cleanse" the URL and avoid the redundancy between and inner app route.
SemObject-display?someID=102343333&/SomeView(102343333)/
which after navigation may look like
SemObject-display?someID=102343333&/SomeView(102343999)/
App was started with 102343333, but then user navigated within the app to another item (102343999).
Any change in the "Shell-part" of the has (SemObject-display?someID102343333) will lead to a cross-app-navigation (reinstantiation of your component) with a different startupParameter.
(There are cases where this is desired in the flow, e.g. a cross navigation from a OrgUnit factsheet to the parent OrgUnit factsheet via a link).
There were ideas within SAP to fuse the inner-app routes and the intent parameters, but they were not carried out, as it's mostly url aesthetics.
Note: To support boomarking, one has to respect both startup parameters and
inner app route during component instantiation,
assuming the user created a bookmark on
SemObject-display?someID=102343333&/SomeView(102343999)/
(While he was looking at 9999(!)).
When reinstantiating the app, the inner app route should take higher precedence than startup-parameters.
So amend the code to:
var oComponentData = this.getComponentData();
if (oComponentData.startupParameters) {
if (sap.ui.core.getHashChanger().getHash()=== "") {
// if no inner app route present, navigate
oRouter.navTo("SomeView", {
someId : oComponentData.startupParameters.someID[0],
}, false);
}
}
https://sapui5.netweaver.ondemand.com/#docs/api/symbols/sap.ushell.services.CrossApplicationNavigation.html
SAP Fiori Launchpad for Developers, Navigation Concept
http://www.sdn.sap.com/irj/scn/go/portal/prtroot/docs/library/uuid/907ae317-cb47-3210-9bba-e1b5e70e5c79?QuickLink=index&overridelayout=true&59575491523067
I was having issues navigating from a Fiori elements app in to a deep page in a freestyle UI5 app and then answer from #user6649841 provided most the solution for my requirement.
In my instance, navigating from the elements list report (app "A") in to the target freestyle app (app "B") I didn't want the worklist/initial page in app B to display at all and instead go straight to the detail page without a flickering of the initial app screen.
The below worked for me, note though it doesn't solve the ugly URL issues. In my case I'm not fussed about it as my nav back will nav back to the elements list report (App A) and never show the worklist page in App B so the user will never make another search on top of this URL which would lead with inconsistent inner and outer keys
Component.js (at end of init function after all the standard sap code, but before router initialization):
var oComponentData = this.getComponentData();
var startupParams = oComponentData.startupParameters;
if (startupParams && startupParams.myQueryStringParamName && startupParams.myQueryStringParamName[0]) {
//In my case using hash changer as I dont want the original landing page (default route) to be
//in the history, so the detail page loads straight away and nav back will cause to nav back to App A
var hashChanger = sap.ui.core.routing.HashChanger.getInstance();
hashChanger.replaceHash("detailPage/" + startupParams.myQueryStringParamName[0]);
}
//initialise after the above so the new hash is set and it doesnt initially load the
//page assigned to the default route causing a flickering and nav slide effect
this.getRouter().initialize();
Looking at the UI5 SDK in UI5 1.48 and above in the initialize method of router you can pass in a boolean to tell it to ignore the initial hash so possibly can do a simpler implementation in newer releases of UI5
Is Component.js right place for handling the startup parameters?
Depends,if you have multiple views and you want to dynamically route based on the incoming parameters. Else you can handle in specific view also.
Your second question was not quite clear to me.
Nevertheless, if you want to only specific cases of startup parameters, then from Source App, set some flag to understand where is the request coming from and handle accordingly. So this way, your normal navigation won't be tampered.

CQ5.5: getting Style of a target page

I've been working on this for sometime now, and I keep running into a wall. I think I'm close, but I figured someone out here in the land of SO might have some deeper insight if not a better way of doing what I'm trying to do.
Basically lets look at this scenario. I have a logo w/ some text that can be set from a few different places. If we look at the setup here is what it looks like.
Hiearchy:
Homepage [has designPath]
- Child Microsite Page [has designPath]
- Logo Component
Logic Flow (in logo component):
if properties.get("logoText") {
use this
} else if currentStyle.get("logoTextFromStyle") {
use this
} else if parentStyle.get("logoTextFromGlobal") {
use this
} else {
be blank
}
My query is with how to get the "parentStyle" of this page. Looking at the docs here: http://dev.day.com/docs/en/cq/5-5/javadoc/com/day/cq/wcm/api/designer/Style.html
I've been able to come up with the fact that I can get a Style object from the "designer" object made available via defineObjects. This is defined with the other utility objects like "pageManager, resourceUtil, resource, currentPage, etc".
With that being said this doesn't seem to work.
//assuming we have getting homePage earlier and it is a valid cq Page resource
Resource homePageResource.slingRequest.getResourceResolver().getResource(homePage.getPath());
Style homePageStyle = designer.getStyle(homePageResource);
at this point homePageStyle is null. To do some more testing I i tried passing currentPage.getPath() instead of homePage.getPath(). I assumed this would give me the currentPage resource and would in end yield the currentStyle object. This also resulted in a null Style object. From this I think I can safely conclude I'm passing the incorrect resource type.
I attempted to load the the cq:designPath into the resource hoping to get a Designer resourceType but to no avail.
I am curious if anyone has run into this problem before. I apologize if I've gone into too much detail, but I wanted to lay out the "why" to my question as well, just in case there was a better way overall of accomplishing this.
I've figured out how to return the style. Here is the rundown of what I did.
//get your page object
Page targetPage = pageManager.getPage("/path/to/target");
//get the Design object of the target page
Design homePageDesign = designer.getDesign(homePage);
//extract the style from the design using the design path
Style homePageStyle = homePageDesign.getStyle(homePageDesign.getPath());
it's very interesting the definition of "getStyle" is a little different from the designer.getStyle vs a Design.getStyle. designer.getStyle asks for a resource whereas Design.getStyle will take the path to a Design "cell" and return the appropriate Style.
I did some testing and it looks like it does work with inherited Styles/Designs. So if my cq:designPath is set at level 1 and I look up a page on at level 2 they will return the Design/Style at the cq:designPath set at level 1.
I hope this helps someone else down the way.
I tried this approach but was not getting the Styles in the Style object.
When we do this:
Design homePageDesign = designer.getDesign(homePage);
In this Design object we get the path till the project node i.e etc/design/myproject
After this if we try to extract the Style from the design path we do not get it.
However I implemented it in a different way.
In the design object, we also get the complete JSON of designs for(etc/design/myproject).
Get the sling:resourceType of the target page and get the value after last index of "/".
Check if this JSON contains the last value. If it contains, you can get your styles, i.e. image, etc.

Translate asp.net mvc 2 application?

I'm hoping this is a rather simple question, but I'm pretty new to MVC and can't see clearly how it should be done. I have a site that I need to translate to another language. I've tried to search on this, but all I found was pretty complex translations about how to handle strings etc in resx files. That may be something for later, but for now all I want is to be able to let the user switch language (by links I can place in the master page), and then based on that choice have different pages shown in different languages.
From my search it seemed this could be achieved by routing somehow. As suggested in another post:
routes.MapRoute(
"Default",
"{language}/{controller}/{action}/{id}",
new { language = "en", controller = "Home", action = "Index", id = "" }
);
And the master page switch links:
<li><%= Html.ActionLink(
"Spanish",
ViewContext.RouteData.Values["action"].ToString(),
new { language = "es" })%></li>
<li><%= Html.ActionLink(
"French",
ViewContext.RouteData.Values["action"].ToString(),
new { language = "fr" })%></li>
<li><%= Html.ActionLink(
"English",
ViewContext.RouteData.Values["action"].ToString(),
new { language = "en" })%></li>
I could try this, but what I don't understand is, what type of routes does this create? Is it "language/controllername/actionname"? And if so, where does it lead? I mean, usually, with just a controller and an action, all I have is one controller and one view, and as long as that view exists it will work. But what is the language in this? Is it just as a folder, so if I have a folder say en-GB/Home such a route would work? That doesn't make sense, so I guess not. So how do I actually make these routes lead somewhere? Where do I place the translated views?
I think using resource files instead will be easier in the long run and not that hard to get going with.
Check out this link for more information.
Here's a quick how to on it.
Here's some gotchas to using resources in .Net MVC with solutions.
re the url, it is like you said / like it reads - language/controllername/actionname
re what it calls - what you need to focus on to understand it is in this bit of the route definition:
new { language = "en", controller = "Home", action = "Index", id = "" }
{controller}/{action}, matches the corresponding controller and action like before. Language and id matches those parameters in the action method you define. Those could also be properties of the (view)model, if that's the parameter you have in the method.
I don't think there is anything automatically hooked for the languages in mvc, so you have to explicitly decide how you want to handle it. One way would be for your action methods to return a view in a subfolder for each language or by adding the language as part of the file name.
Another way to go about it, is to define a handler in the route that sets the thread ui culture as you would in classic asp.net. From then on you use the asp.net mvc resources like in klabranche links.