How to overlay a cq widget so it is also available in SiteAdmin - aem

The CQ.tagging.TagInputField provided two configuration parameter which won't work in combination:
tagsBasePath
namespaces
Using the OOTB facebook tags as example, I want to restric the dialog to only display the Favorite Teams. The Structure is this:
So I set tagBasePath to /etc/tags/facebook and namespaces to [favorite_teams]. This does what it is supposed to do and only shows the two teams in the dialog. But when you click on it, a JavaScript exceptions is thrown. The problem lies in the following method defined in /libs/cq/tagging/widgets/source/CQ.tagging.js
CQ.tagging.parseTag = function(tag, isPath) {
var tagInfo = {
namespace: null,
local: tag,
getTagID: function() {
return this.namespace + ":" + this.local;
}
};
// parse tag pattern: namespace:local
var colonPos = tag.indexOf(isPath ? '/' : ':');
if (colonPos > 0) {
// the first colon ":" delimits a namespace
// don't forget to trim the strings (in case of title paths)
tagInfo.namespace = tag.substring(0, colonPos).trim();
tagInfo.local = tag.substring(colonPos + 1).trim();
}
return tagInfo;
};
It does not respect the configurations set on the widget and returns a tagInfo where the namespace is null. I then overlayed the method in my authoring JavaScripts, but this is of course not working in the SiteAdmin as my custom JS are not included.
So, do I really have to overwrite the CQ.tagging.js below libs or can I somehow inject my overlay into the SiteAdmin so the PageProperties Dialog opened from there works as well?
UPDATE: I had a chat with Adobe support regarding this and it was pointed out that if you use tagsBasePath you need to place it somewhere else than below /etc/tags. But this won't work as well as the TagListServlet will return no tags as /etc/tags is also fixed in the TagManager as the tagsBasePath. I now overwrite the above mentioned js at its location, being well aware that I need to check it if we install a hotfix or an update. Is someone has a more elegant solution I'd be still thankful.

Related

How to add own tags using the tagify plugin, 'Mix text & tags' in particular

I'm creating an input field mixed with tags and inputs using the tagify plugin- section-mix- (see: https://yaireo.github.io/tagify/#section-mix).
The problem is:
Note that tags can only be created if the value matches any of the whitelisted item's value.
I want to add them myself by hitting enter just as in the other examples in the link. Any idea how to solve this with tagify?
HTML
<textarea name='mix'>#cartman and #kyle do not know #homer who is #lisa</textarea>
JAVASCRIPT
var input = document.querySelector('[name=mix]'),
// init Tagify script on the above inputs
tagify = new Tagify(input, {
mode : 'mix', // <-- Enable mixed-content
pattern : /#/, // <-- Tag words which start with # (can be a String instead of Regex)
whitelist : [
{
value: 'Homer'
}
],
dropdown : {
enabled : 1
}
})
var whitelist_2 = ['Homer', 'Marge', 'Bart', 'Lisa', 'Maggie', 'Mr. Burns', 'Ned', 'Milhouse', 'Moe'];
// A good place to pull server suggestion list accoring to the prefix/value
tagify.on('input', function(e){
var prefix = e.detail.prefix;
if( prefix == '#' )
tagify.settings.whitelist = whitelist_2;
if( e.detail.value.length > 1 )
tagify.dropdown.show.call(tagify, e.detail.value);
}
Tagify now supports creating new tags in mix-mode as of version 3.4.0
See tagify's example page
UPDATE 24 FEB, 2020
Currently only tags without spaces can be added due to unresolved complexity in the implementation of how adding new tags in mix-mode works. I will address this soon.

How to get images in the product list view with an API call in Shopware?

I’m working on a single-page-application with fully client-side rendered frontend (React) and Shopware acting as a headless CMS in the background. So all product-data will be pulled from the API and checkout will also use the API to send the data.
My problem is the following:
When trying to render the product list page, I call the articles endpoint which returns the basic info of all my products. The problem is that I would also need to render the main image associated with each product and this list does not expose any data from the media attached to the products.
I can get the media item(s) for a product using the Media endpoint, problem is that this one expects a Media Id which I cannot get from the listed representations of the articles.
So right now the only way I see to get the images is first making a call that gets all the products, then loop through them and get each product’s details, calling the article endpoint again with each product ID, then when I have the media IDs I loop through those and get the images for each product using the media endpoint, because that’s the only one that exposes the actual image path in the response. This seems way too complicated and slow.
Is there a smarter way to do this? Can I get Shopware to output the 1st image’s path in the Article list response, that’s associated with the current product?
Also I saw that the paths to the images look like this:
/media/image/f5/fb/95/03_200x200.jpg
The first part up to /media/image/ is fixed, that’s straight forward, and I get the image’s filename and extension in the article detail’s response in the media object and even the file extension, which looks like this in the API's response.
The problem is that I don’t know what’s the f5/fb/95/ stand for. If I could get this info from the details I could assemble the URL for the image programmatically and then I wouldn’t need the call to the media endpoint.
You can see how the path is generated in the class \Shopware\Bundle\MediaBundle\Strategy\Md5Strategy of Shopware's source code. The two relevant methods are:
<?php
public function normalize($path)
{
// remove filesystem directories
$path = str_replace('//', '/', $path);
// remove everything before /media/...
preg_match("/.*((media\/(?:archive|image|music|pdf|temp|unknown|video|vector)(?:\/thumbnail)?).*\/((.+)\.(.+)))/", $path, $matches);
if (!empty($matches)) {
return $matches[2] . '/' . $matches[3];
}
return $path;
}
public function encode($path)
{
if (!$path || $this->isEncoded($path)) {
return $this->substringPath($path);
}
$path = $this->normalize($path);
$path = ltrim($path, '/');
$pathElements = explode('/', $path);
$pathInfo = pathinfo($path);
$md5hash = md5($path);
if (empty($pathInfo['extension'])) {
return '';
}
$realPath = array_slice(str_split($md5hash, 2), 0, 3);
$realPath = $pathElements[0] . '/' . $pathElements[1] . '/' . join('/', $realPath) . '/' . $pathInfo['basename'];
if (!$this->hasBlacklistParts($realPath)) {
return $realPath;
}
foreach ($this->blacklist as $key => $value) {
$realPath = str_replace($key, $value, $realPath);
}
return $realPath;
}
Step for step, this happens generally to an image path, for example https://example.com/media/image/my_image.jpg, upon passing it to the encode-method:
The normalize method strips everything except for media/image/my_image.jpg
An md5-hash is generated out of the resulting string: 5dc18cdfa0...
The resulting string from step 1 is split at every /: ['media', 'image', 'my_image.jpg']
The first three character pairs from the md5-hash are being stored into an array: ['5d', 'c1', '8c']
The final path is being assembled out of the arrays from steps 3 and 4: media/image/5d/c1/8c/my_image.jpg
Every occurence of /ad/ is being replaced by /g0/. This is done, because there are ad-blockers which block requests to URLs containing /ad/
Shopware computes the path for you.
There is acutally no need to know about the hashed path...
You will be redirected to the correct path.
Try the following:
/media/image/ + image['path'] + image['extension']
An for the thumbnails:
/media/image/thumbnail/ + image['path'] + '_200x200' + image['extension']
or
/media/image/thumbnail/ + image['path'] + '_600x600' + image['extension']

xPages REST Service Results into Combobox or Typeahead Text Field

I've read all the documentation I can find and watched all the videos I can find and don't understand how to do this. I have set up an xPages REST Service and it works well. Now I want to place the results of the service into either a combobox or typeahead text field. Ideally I would like to know how to do it for both types of fields.
I have an application which has a view containing a list of countries, another view containing a list of states, and another containing a list of cities. I would like the first field to only display the countries field from the list of data it returns in the XPages REST Service. Then, depending upon which country was selected, I would like the states for that country to be listed in another field for selection, etc.
I can see code for calling the REST Service results from a button, or from a dojo grid, but I cannot find how to call it to populate either of the types of fields identified above.
Where would I call the Service for the field? I had thought it would go in the Data area, but perhaps I've just not found the right syntax to use.
November 6, 2017:
I have been following your suggestion, but am still lost as can be. Here's what I currently have in my code:
x$( "#{id:ApplCountry}" ).select2({
placeholder: "select a country",
minimumInputLength: 2,
allowClear : true,
multiple: false,
ajax: {
dataType: 'text/plain',
url: "./Application.xsp/gridData",
quietMillis: 250,
data: function (params) {
return {
search:'[name=]*'+params.term+'*',
page: params.page
};
},
processResults: function (data, page) {
var data = $.map(data, function (obj) {
obj.id = obj.id || obj["#entityid"];
obj.text = obj.text || obj.name;
return obj;
});
},
return {results: data};
}
}
});
I'm using the dataType of 'text/plain' because that was what I understood I should use when gathering data from a domino application. I have tried changing this to json but it makes no difference.
I'm using processResults because I understand this is what should be used in version 4 of select2.
I don't understand the whole use of the hidden field, so I've stayed away from that.
No matter what I do, although my REST service works if I put it directly in the url, I cannot get any data to display in the field. All I want to display in the field is the country code of the document, which is in the field named "name" (not my choice, it's how it came before I imported the data from MySQL.
I have read documentation and watched videos, but still don't really understand how everything fits together. That was my problem with the REST service. If you use it in Dojo, you just put the name of the service in a field on the Dojo element and it's done, so I don't understand why all the additional coding for another type of domino element. Shouldn't it work the same way?
I should point out that at some points it does display the default message, so it does find the field. Just doesn't display the country selections.
I think the issue may be that you are not returning SelectItems to your select2, and that is what it is expecting. When I do something like you are trying, I actually use a bean to generate the selection choices. You may want to try that or I'm putting in the working part of my bean below.
The Utils.getItemValueAsString is a method I use to return either the string value of a field, or if it is not on the document/empty/null an empty string. I took out an if that doesn't relate to this, so there my be a mismatch, but I hope not.
You might be able to jump directly to populating the arrayList, but as I recall I needed to leverage the LinkedHashMap for something.
You should be able to do the same using SSJS, but since that renders to Java before executing, I find this more efficient.
For label/value pairs:
LinkedHashMap lhmap = new LinkedHashMap();
Document doc = null;
Document tmpDoc = null;
allObjects.addElement(doc);
if (dc.getCount() > 0) {
doc = dc.getFirstDocument();
while (doc != null) {
lhmap.put(Utils.getItemValueAsString(doc, LabelField, true), Utils.getItemValueAsString(doc, ValueField, true));
}
tmpDoc = dc.getNextDocument(doc);
doc.recycle();
doc = tmpDoc;
}
}
List<SelectItem> options = new ArrayList<SelectItem>();
Set set = lhmap.entrySet();
Iterator hsItr = set.iterator();
while (hsItr.hasNext()) {
Map.Entry me = (Map.Entry) hsItr.next();
// System.out.println("after: " + hStr);
SelectItem option = new SelectItem();
option.setLabel(me.getKey() + "");
option.setValue(me.getValue() + "");
options.add(option);
}
System.out.println("About to return from generating");
return options;
}
I ended up using straight up SSJS. Worked like a charm - very simple.

Wordpress TinyMCE: Switch Views

In order to complete my Wordpress Plugin I want to make tinyMCE to switch between a custom Tag (Some|data|here) and a corresponding Image Display in the WYSIWYG-View.
The event should be triggered on load, safe, autosave, switch view etc. Threre are 4 different events defined, but none of them works as expected.
onBeforeSetContent
onGetContent
onPostProcess
onLoadContent
.
ed.onPostProcess.add(function(ed, o) {
if (o.set){
o.content = t._htmlToWysiwyg(o.content, url);
}
if (o.get){
o.content = t._wysiwygToHtml(o.content, t);
}
});
Anyon know the right way?
I do not know what you expect the 4 different events will do (?), but i can see some problems in your code.
1. The object o does not contain fields get and set - so o.get and o.set will never be true! Thus your code will never be called.
2. You are using the variable url, but this one is not defined here.
Working example: You may try to paste a string containing "a" into the editor. Use the following:
ed.onPostProcess.add(function(ed, o) {
//console.log('o:', o);
o.content = o.content.replace(/a/g, "A");
});
You should see that all lower 'a's get replaced by 'A's.

KRL and Yahoo Local Search

I'm trying to use Yahoo Local Search in a Kynetx Application.
ruleset avogadro {
meta {
name "yahoo-local-ruleset"
description "use results from Yahoo local search"
author "randall bohn"
key yahoo_local "get-your-own-key"
}
dispatch { domain "example.com"}
global {
datasource local:XML <- "http://local.yahooapis.com/LocalSearchService/V3/localsearch";
}
rule add_list {
select when pageview ".*" setting ()
pre {
ds = datasource:local("?appid=#{keys:yahoo_local()}&query=pizza&zip=#{zip}&results=5");
rs = ds.pick("$..Result");
}
append("body","<ul id='my_list'></ul>");
always {
set ent:pizza rs;
}
}
rule add_results {
select when pageview ".*" setting ()
foreach ent:pizza setting pizza
pre {
title = pizza.pick("$..Title");
}
append("#my_list", "<li>#{title}</li>");
}
}
The list I wind up with is
. [object Object]
and 'title' has
{'$t' => 'Pizza Shop 1'}
I can't figure out how to get just the title. It looks like the 'text content' from the original XML file turns into {'$t' => 'text content'} and the '$t' give problems to pick().
When XML datasources and datasets get converted into JSON, the text value within an XML node gets assigned to $t. You can pick the text of the title by changing your pick statement in the pre block to
title = pizza.pick("$..Title.$t");
Try that and see if that solves your problem.
Side notes on things not related to your question to consider:
1) Thank you for sharing the entire ruleset, what problem you were seeing and what you expected. Made answering your question much easier.
2) The ruleset identifier should not be changed from what AppBuilder or the command-line gem generate for you. Your identifier that is currently
ruleset avogadro {
should look something more like
ruleset a60x304 {
3) You don't need the
setting ()
in the select statement unless you have a capture group in your regular expression
Turns out that pick("$..Title.$t") does work. It looks funny but it works. Less funny than a clown hat I guess.
name = pizza.pick("$..Title.$t");
city = pizza.pick("$..City.$t");
phone = pizza.pick("$..Phone.$t");
list_item = "<li>#{name}/#{city} #{phone}</li>"
Wish I had some pizza right now!