Goal
Assets Bulk Update Editor provides Append option for adding Multi-valued data (like cq:tags)
This post extends the editor and provides an option to remove previously selected values
Demo | Package Install | Github
Product
Extension
Solution
1) Login to CRXDE Lite (http://localhost:4502/crx/de), create folder /apps/eaem-bulk-update-remove-tags
2) Create node /apps/eaem-bulk-update-remove-tags/clientlib of type cq:ClientLibraryFolder, add String property categories with value dam.gui.coral.metadataeditor, String[] property dependencies with value lodash
3) Create file (nt:file) /apps/eaem-bulk-update-remove-tags/clientlib/js.txt, add
bulk-update-remove-tags.js
4) Create file (nt:file) /apps/eaem-bulk-update-remove-tags/clientlib/bulk-update-remove-tags.js, add the following code for extending autocomplete in metadata editor and provide the Remove selected tags checkbox (when checked sends the parameter suffixed with eaem-remove e.g. cq:tags-eaem-remove read by the sling post processor in step 5)
(function($, $document) {
var extended = false,
FOUNDATION_SELECTIONS_CHANGE = "foundation-selections-change",
FOUNDATION_CONTENT_LOADED = "foundation-contentloaded",
DAM_ADMIN_CHILD_PAGES_SEL = ".cq-damadmin-admin-childpages",
REMOVE_EAEM_CB_SUFFIX = "-eaem-remove";
$document.on(FOUNDATION_CONTENT_LOADED, addRemoveTags);
$document.on(FOUNDATION_SELECTIONS_CHANGE, DAM_ADMIN_CHILD_PAGES_SEL , showHideRemoveCheckbox);
function showHideRemoveCheckbox(event){
var $collection = $(event.target),
selectApi = $collection.adaptTo("foundation-selections"),
count = selectApi.count(),
$acFields = $("foundation-autocomplete"), $removeCB, $acField;
_.each($acFields, function(acField){
$acField = $(acField);
$removeCB = getRemoveCheckBox($acField);
if(count === 1){
$removeCB.attr("disabled", "disabled");
}else{
$removeCB.removeAttr("disabled").removeAttr("checked");
}
});
}
function addRemoveTags(){
if(extended){
return;
}
var $acFields = $("foundation-autocomplete"), $acField,
$removeCB, cbName;
if(_.isEmpty($acFields)){
return;
}
_.each($acFields, function(acField){
$acField = $(acField);
cbName = $acField.attr("name") + REMOVE_EAEM_CB_SUFFIX;
if(!_.isEmpty(getRemoveCheckBox($acField))){
return;
}
$removeCB = $(getRemoveHtml(cbName)).insertBefore($acField);
});
}
function getRemoveCheckBox($acField){
var cbName = $acField.attr("name") + REMOVE_EAEM_CB_SUFFIX;
return $acField.prev("[name='" + cbName + "']");
}
function getRemoveHtml(cbName){
return '<coral-checkbox class="coral-Form-field" name="' + cbName + '" value="true">Remove selected tags</coral-checkbox>';
}
})(jQuery, jQuery(document));
5) Add a Sling Post Processor apps.experienceaem.assets.EAEMBulkUpdateRemovePostProcessor for reading eaem-remove parameters and remove selected values
package apps.experienceaem.assets;
import org.apache.commons.lang.ArrayUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.request.RequestParameter;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.servlets.post.Modification;
import org.apache.sling.servlets.post.SlingPostProcessor;
import org.json.JSONObject;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import javax.jcr.*;
import java.util.*;
@Component(
immediate = true,
service = { SlingPostProcessor.class },
property = {
Constants.SERVICE_RANKING + ":Integer=-99"
}
)
public class EAEMBulkUpdateRemovePostProcessor implements SlingPostProcessor {
private static final String MODE = "mode";
private static final String MODE_SOFT = "soft";
private static final String BULK_UPDATE = "dam:bulkUpdate";
private static final String EAEM_REMOVE_SUFFIX = "-eaem-remove";
public void process(SlingHttpServletRequest request, List<Modification> modifications) throws Exception {
ResourceResolver resolver = request.getResourceResolver();
String reqType = request.getParameter(BULK_UPDATE);
String reqMode = request.getParameter(MODE);
if ((reqType == null) || (reqMode == null) || !reqMode.equals(MODE_SOFT)) {
return;
}
Session session = resolver.adaptTo(Session.class);
RequestParameter[] assets = request.getRequestParameters("asset");
if ( (session == null) || (assets == null)) {
return;
}
session.refresh(true);
Map<String, String[]> removalMap = getValuesForRemoval(request);
if(removalMap.isEmpty()){
return;
}
for (RequestParameter asset : assets) {
JSONObject assetJson = new JSONObject(asset.toString());
processChanges(session, assetJson, removalMap);
}
session.save();
}
private Map<String, String[]> getValuesForRemoval(SlingHttpServletRequest request){
Map<String, String[]> removalMap = new HashMap<String, String[]>();
Map<String, String[]> params = request.getParameterMap();
String removeKey = null;
String[] removeValues = null;
for(String param : params.keySet()){
if(!param.endsWith(EAEM_REMOVE_SUFFIX)) {
continue;
}
removeKey = param.substring(0, param.lastIndexOf(EAEM_REMOVE_SUFFIX));
removeValues = params.get(removeKey);
if(removeValues == null){
continue;
}
removalMap.put(removeKey.substring(removeKey.lastIndexOf("/") + 1), removeValues);
}
return removalMap;
}
private void processChanges(Session session, JSONObject assetJson, Map<String, String[]> removalMap)
throws Exception{
String assetPath = assetJson.getString("path");
String metadataPath = assetPath + "/jcr:content/metadata";
Node metadataNode = session.getNode(metadataPath);
String removePropertyName;
Property property;
for(String removeKey : removalMap.keySet()){
if(!metadataNode.hasProperty(removeKey)){
continue;
}
property = metadataNode.getProperty(removeKey);
if(!property.isMultiple()){
continue;
}
Value values[] = getNewValues(property.getValues(), Arrays.asList(removalMap.get(removeKey)));
if(ArrayUtils.isEmpty(values)){
property.remove();
}else{
metadataNode.setProperty(removeKey, values);
}
removePropertyName = removeKey + EAEM_REMOVE_SUFFIX;
if(metadataNode.hasProperty(removePropertyName)){
metadataNode.getProperty(removePropertyName).remove();
}
}
}
private Value[] getNewValues(Value[] oldValues, List<String> removeValues) throws Exception{
List<Value> newValues = new ArrayList<Value>();
for(Value value : oldValues){
if(removeValues.contains(value.getString())){
continue;
}
newValues.add(value);
}
return newValues.toArray(new Value[newValues.size()]);
}
}