How to filter tags in a component dialog. Adobe CQ - aem

I am trying to filter the tags in a component dialog. I know that I can filter it by namespace, however that applies only to root level. Can I filter the tag selection one level deeper?
for example:
etc
tags
namespace
article-type
blog
news
asset-type
image
video
I want to filter the tags in the component dialog so the user can only select the tags under 'article-type'.
Thanks,

Yes and no. Officially you can go deeper according to the widget API, but there is a "bug" in the Widget JavaScript file that prevents it to work. I had the same issue and I just overwrite this JavaScript file.
Widget definition:
<article jcr:primaryType="cq:Widget"
fieldLabel="Article Type"
name="./cq:tags"
tagsBasePath="/etc/tags/namespace"
xtype="tags">
<namespaces jcr:primaryType="cq:WidgetCollection">
<ns1 jcr:primaryType="nt:unstructured" maximum="1" name="article-type" />
</namespaces>
</article>
<asset jcr:primaryType="cq:Widget"
fieldLabel="Asset Type"
name="./cq:tags"
namespaces="[asset-type]"
tagsBasePath="/etc/tags/offering"
xtype="tags"/>
In this case only one Tag below article-type can be selected; you can limit the number with the maximum attribute. The asset-type has no limits. So choose the option that suits your need.
JavaScript overwrite:
To make this work, you need to change the method CQ.tagging.parseTag in /libs/cq/tagging/widgets/source/CQ.tagging.js:
// private - splits tagID into namespace and local (also works for title paths)
CQ.tagging.parseTag = function(tag, isPath) {
var tagInfo = {
namespace: null,
local: tag,
getTagID: function() {
return this.namespace + ":" + this.local;
}
};
var tagParts = tag.split(':');
if (tagParts[0] == 'article-type' || tagParts[0] == 'asset-type') {
var realTag = tagParts[1];
var pos = realTag.indexOf('/');
tagInfo.namespace = realTag.substring(0, pos).trim();
tagInfo.local = realTag.substring(pos + 1).trim();
}
else {
// 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;
};

Related

highlight a changed property on model load

I have a table that where the data is periodically updated by a javascript interval function in my controller:
var model = this.getview().getModel();
var updateModel = setInterval(function(){
model.loadData('path/to/my/data.json');
}, 30000)
This will basically be static display on a public monitor showing a summary of data.
I want to be able to highlight when a property has changed, so I've been trying to add a class to the control when it changes. The class will then highlight this in some way with CSS.
<Table items="{items}">
<columns>
<Column/>
<Column/>
</columns>
<items>
<ColumnListItem>
<cells>
<Text
text="{name}" />
<ObjectStatus
text="{value}"
state="{
path: 'value',
formatter: '.formatter.redOrGreen'
}"/>
</cells>
</ColumnListItem>
</items>
</Table>
So the model updates every 30 seconds. If the {value} field changes, I want to add a class to ObjectStatus control.
At the moment I'm just using a JSON model for local development to see if this is possible, but in production it will be an oData service.
Thanks for the answers, I managed to solve this, but my method wasn't quite covered by the answers on here. This is how I did it:
The requirements for this changed slightly since I posted the question. I'll need to indicate if something has changed, but also if the value has gone up or down. I'll also need to indicate if something goes above or below a certain value. I also wanted to make a solution that could be easily adapted if there are any other future requirements. This will also need to be easily adapted for oData when the backend service is up and running.
First of all (and key to this) is setting up a duplicate model, so this goes into my component.js file .I'm just duplicating the model here so that the values old and new values are unchanged, to make the formatter functions work on the first page load:
var oModel = new JSONModel('/path/to/data.js');
this.setModel(oModel, 'model');
this.setModel(oModel, 'oldModel');
In the controller for my view, I then take a copy of the old data, which goes into the old model that I've attached to the view, the new model is then updated. I do this in the after rendering hook to optimize the initial page load.
onAfterRendering: function(){
var thisView = this.getView();
var updateModel = function(){
var oldData = thisView.getModel('model').getData();
var oldModel = new JSONModel(oldWharehousesData);
thisView.setModel(ollModel, 'oldModel');
//update model
var newModel = thisView.getModel('model');
model.loadData('/path/to/data.js');
};
window.refershInterval = setInterval(updateModel, 30000);
}
I'm then able to input the new and old values to a formatter in my XML view and output a couple of custom data attribute:
<core:CustomData
key="alert-status"
value="{
parts: [
'model>Path/To/My/Property',
'oldModel>Path/To/My/Property'
],
formatter: '.formatter.alertStatus'
}"
writeToDom="true"/>
</customData>
My formatter.js :
alertStatus: function(newValue, oldValue){
var alertNum = 25;
if(newValue < alertNum && oldValue >= alertNum) {
return 'red';
} else if (newValue >= alertNum && oldValue < alertNum) {
return 'green';
} else {
return 'none';
}
}
I can then have as many custom data attributes as I like, run them through their own formatter function, which can be styled to my heart's content, e.g:
compareValues: function(newValue, oldValue) {
if (newValue > oldValue) {
return 'higher';
} else if (newValue < oldValue){
return 'lower';
} else {
return 'false';
}
}
I have build an example on JSBin.
First you have to get the received data. You can use the
Model.attachRequestCompleted event for that:
this.model = new sap.ui.model.json.JSONModel();
this.model.attachRequestCompleted(this.onDataLoaded, this);
In the event handler onDataLoaded you can retrieve the JavaScript object and compare it to a saved copy. You have to write the flags that indicate changes to the array item itself. (Storing it in a separate model as Marc suggested in his comment would not work because in your aggregation binding you only have the one context to your array item.)
At last you have to save the newData object as this.oldData for the next request.
onDataLoaded:function(){
var newData = this.model.getProperty("/");
if (this.oldData){
//diff. You should customize this to your needs.
for(var i = 0, length = Math.min(newData.items.length, this.oldData.items.length); i< length; i++){
newData.items[i].valueChanged = newData.items[i].value !== this.oldData.items[i].value;
newData.items[i].nameChanged = newData.items[i].name !== this.oldData.items[i].name;
}
}
this.oldData = newData;
this.getView().getModel().setProperty("/",newData);
},
You can then bind the ObjectState state property to the flag(s):
<ObjectStatus
text="{value}"
state="{= ${valueChanged} ? 'Warning':'None' }"/>
If you want to change the background color of the whole row or something like that you can apply Bernard's answer and use the flag(s) in a customData attribute.
You can use the <customData> tag
This allows the insertion of a custom attribute into the HTML produced by the XML to HTML conversion process
In the example below for example I add a custom attribute (my own) - this code generates the following attribute data-colour in a relevant HTML element (a <SPAN> tag) - inspect the relevant element using, say, Chrome.
<customData>
<core:CustomData writeToDom="true" key="colour" value="{vproducts>ListCostColour}" />
</customData>
You are then able to create a style for this attribute in your own style sheet as follows (and reference this in your manifest.json)
[data-colour="red"] {
background-color: #ffd1cc;
}
[data-colour="orange"] {
background-color: rgba(255, 243, 184, 0.64);
}
[data-colour="green"] {`enter code here`
background-color: rgba(204, 255, 198, 0.97);
}

Can anybody help me to resolve ng-grid filtering in a multi row Grid

Here is the plunker: http://plnkr.co/edit/fGVVOOIwvf4GrEj3XtJ6?p=preview
I have created a multi row grid and I would like to filter the grid data for each column header.
If I put an input box outside the grid it is working fine. If I put an input box inside the column header filtering is not working
Please see the code and help me for this.
Use this filterBar plugin. (I cannot take credit for this, I do not remember where I found it.)
Plugin
var filterBarPlugin = {
init: function(scope, grid) {
filterBarPlugin.scope = scope;
filterBarPlugin.grid = grid;
$scope.$watch(function() {
var searchQuery = "";
angular.forEach(filterBarPlugin.scope.columns, function(col) {
if (col.visible && col.filterText) {
var filterText = (col.filterText.indexOf('*') == 0 ? col.filterText.replace('*', '') : "^" + col.filterText) + ";";
searchQuery += col.displayName + ": " + filterText;
}
});
return searchQuery;
}, function(searchQuery) {
filterBarPlugin.scope.$parent.filterText = searchQuery;
filterBarPlugin.grid.searchProvider.evalFilter();
});
},
scope: undefined,
grid: undefined,
};
Change your header cell input ng-model to: col.filterText
<input type="text" placeholder="MY NAME" ng-model="col.filterText" ng-change="activateFilter()"/>
Add plugin to gridOptions
...
plugins: [filterBarPlugin],
...
Updated Plunker: Plunker
I know this is old, but in case someone else ends up here...
Here is a google group thread discussing this: https://groups.google.com/forum/#!topic/angular/lhu5Fbs97G4
and within that discussion you can find the following plnkr which does what you are wanting (and which I think the above answer references):
http://plnkr.co/edit/c8mHmAXattallFRzXSaG?p=preview

Mootools stop form submit method

I don't want to use an <input type=submit /> button to submit a form and I am instead using an <a> element. This is due to styling requirements. So I have this code:
myButton.addEvent('click', function() {
document.id('myForm').submit();
});
However, I have also written a class that improves and implements the placeholder attribute on inputs and textareas:
var FDPlaceholderText = new Class({
Implements: Events,
initialize: function() {
var _self = this;
var forms = document.getElements('form');
forms.each(function(form) { // All forms
var performInit = false;
var i = 0;
var ph = [];
form.getElements('input, textarea').each(function(el) { // Get form inputs and textareas
if (el.getProperty('placeholder') != null) { // Check for placeholder attribute
performInit = true;
ph[i] = _self.initPlaceholder(el); // Assign the placeholder replacement to the elements
}
i ++;
});
if (performInit) {
_self.clearOnSubmit(form, ph);
}
});
},
clearOnSubmit: function(form, ph) {
form.addEvent('submit', function(e) {
ph.each(function(el) {
if (el.value == el.defaultValue) {
el.value = '';
}
});
});
},
initPlaceholder: function(el) {
el.defaultValue = el.getProperty('placeholder');
el.value = el.getProperty('placeholder');
el.addEvents({
'focus': function() {
if (el.value == el.defaultValue) el.value = '';
},
'blur': function() {
if(el.value.clean() == ''){
el.value = el.defaultValue;
}
}
});
return el;
}
});
window.addEvent('domready', function() {
new FDPlaceholderText();
});
The above class works great if a form is submitted using an actual <input type=submit /> button: it listens for a submit and clears the inputs values if they are still the default ones therefore validating that they are essentially empty.
However, it seems that because I am submitting one of my forms by listening to a click event on an <a> tag the form.addEvent('submit', function(e) { isn't getting fired.
Any help is appreciated.
well you can change the click handler to fireEvent() instead of call the .submit() directly:
myButton.addEvent('click', function() {
document.id('myForm').fireEvent('submit');
});
keep in mind a couple of things (or more).
placeholder values to elements that lack placeholder= attribute is pointless
if you detect placeholder support, do so once and not on every element, it won't change suddenly midway through the loop. you can go something like var supportsPlaceholder = !!('placeholder' in document.createElement('input')); - remember, there is no need to do anything if the browser supports it and currently, near enough 60% do.
you can otherwise do !supportsPlaceholder && el.get('placeholder') && self.initPlaceholder(el); - which avoids checking attributes when no need
when the form is being submitted you really need to clear placeholder= values in older browser or validation for 'required' etc will fail. if validation still fails, you have to reinstate the placeholder, so you need a more flexible event pattern
avoid using direct references to object properties like el.value - use the accessors like el.get('value') instead (for 1.12 it's getProperty)
for more complex examples of how to deal with this in mootools, see my repo here: https://github.com/DimitarChristoff/mooPlaceholder
This is because the submit() method is not from MooTools but a native one.
Maybe you can use a <button type="submit"> for your styling requirements instead.

Handle selected event in autocomplete textbox using bootstrap Typeahead?

I want to run JavaScript function just after user select a value using autocomplete textbox bootstrap Typeahead.
I'm searching for something like selected event.
$('.typeahead').on('typeahead:selected', function(evt, item) {
// do what you want with the item here
})
$('.typeahead').typeahead({
updater: function(item) {
// do what you want with the item here
return item;
}
})
For an explanation of the way typeahead works for what you want to do here, taking the following code example:
HTML input field:
<input type="text" id="my-input-field" value="" />
JavaScript code block:
$('#my-input-field').typeahead({
source: function (query, process) {
return $.get('json-page.json', { query: query }, function (data) {
return process(data.options);
});
},
updater: function(item) {
myOwnFunction(item);
var $fld = $('#my-input-field');
return item;
}
})
Explanation:
Your input field is set as a typeahead field with the first line: $('#my-input-field').typeahead(
When text is entered, it fires the source: option to fetch the JSON list and display it to the user.
If a user clicks an item (or selects it with the cursor keys and enter), it then runs the updater: option. Note that it hasn't yet updated the text field with the selected value.
You can grab the selected item using the item variable and do what you want with it, e.g. myOwnFunction(item).
I've included an example of creating a reference to the input field itself $fld, in case you want to do something with it. Note that you can't reference the field using $(this).
You must then include the line return item; within the updater: option so the input field is actually updated with the item variable.
first time i've posted an answer on here (plenty of times I've found an answer here though), so here's my contribution, hope it helps. You should be able to detect a change - try this:
function bob(result) {
alert('hi bob, you typed: '+ result);
}
$('#myTypeAhead').change(function(){
var result = $(this).val()
//call your function here
bob(result);
});
According to their documentation, the proper way of handling selected event is by using this event handler:
$('#selector').on('typeahead:select', function(evt, item) {
console.log(evt)
console.log(item)
// Your Code Here
})
What worked for me is below:
$('#someinput').typeahead({
source: ['test1', 'test2'],
afterSelect: function (item) {
// do what is needed with item
//and then, for example ,focus on some other control
$("#someelementID").focus();
}
});
I created an extension that includes that feature.
https://github.com/tcrosen/twitter-bootstrap-typeahead
source: function (query, process) {
return $.get(
url,
{ query: query },
function (data) {
limit: 10,
data = $.parseJSON(data);
return process(data);
}
);
},
afterSelect: function(item) {
$("#divId").val(item.id);
$("#divId").val(item.name);
}
Fully working example with some tricks. Assuming you are searching for trademarks and you want to get the selected trademark Id.
In your view MVC,
#Html.TextBoxFor(model => model.TrademarkName, new { id = "txtTrademarkName", #class = "form-control",
autocomplete = "off", dataprovide = "typeahead" })
#Html.HiddenFor(model => model.TrademarkId, new { id = "hdnTrademarkId" })
Html
<input type="text" id="txtTrademarkName" autocomplete="off" dataprovide="typeahead" class="form-control" value="" maxlength="100" />
<input type="hidden" id="hdnTrademarkId" />
In your JQuery,
$(document).ready(function () {
var trademarksHashMap = {};
var lastTrademarkNameChosen = "";
$("#txtTrademarkName").typeahead({
source: function (queryValue, process) {
// Although you receive queryValue,
// but the value is not accurate in case of cutting (Ctrl + X) the text from the text box.
// So, get the value from the input itself.
queryValue = $("#txtTrademarkName").val();
queryValue = queryValue.trim();// Trim to ignore spaces.
// If no text is entered, set the hidden value of TrademarkId to null and return.
if (queryValue.length === 0) {
$("#hdnTrademarkId").val(null);
return 0;
}
// If the entered text is the last chosen text, no need to search again.
if (lastTrademarkNameChosen === queryValue) {
return 0;
}
// Set the trademarkId to null as the entered text, doesn't match anything.
$("#hdnTrademarkId").val(null);
var url = "/areaname/controllername/SearchTrademarks";
var params = { trademarkName: queryValue };
// Your get method should return a limited set (for example: 10 records) that starts with {{queryValue}}.
// Return a list (of length 10) of object {id, text}.
return $.get(url, params, function (data) {
// Keeps the current displayed items in popup.
var trademarks = [];
// Loop through and push to the array.
$.each(data, function (i, item) {
var itemToDisplay = item.text;
trademarksHashMap[itemToDisplay] = item;
trademarks.push(itemToDisplay);
});
// Process the details and the popup will be shown with the limited set of data returned.
process(trademarks);
});
},
updater: function (itemToDisplay) {
// The user selectes a value using the mouse, now get the trademark id by the selected text.
var selectedTrademarkId = parseInt(trademarksHashMap[itemToDisplay].value);
$("#hdnTrademarkId").val(selectedTrademarkId);
// Save the last chosen text to prevent searching if the text not changed.
lastTrademarkNameChosen = itemToDisplay;
// return the text to be displayed inside the textbox.
return itemToDisplay;
}
});
});

can I build a css class on the fly in tiny mce?

I'm using tiny mce, but I found it adds multiple spans with inline styles to the content for any applied style. Inline styles are not W3c Compliant, so must avoid inline css. Is it possible to create css class on the fly and apply to the selection, while editing content in tiny mce ?
Yes that is possible, but it took me some effort. What needs to be done is to write the class into the head of the editors iframe. Here is some example code which should work for IE,FF, Safari and point you into the right direction:
fonturl = "http://myfonts.com/arial.ttf"
csstext_to_add = '#font-face {font-family: "ownfont";src: url("'+fonturl+'");}'; // example
iframe_id = ed.id;
with(document.getElementById(iframe_id).contentWindow){
var h=document.getElementsByTagName("head");
if (!h.length) {
return;
}
var newStyleSheet=document.createElement("style");
newStyleSheet.type="text/css";
h[0].appendChild(newStyleSheet);
try{
if (typeof newStyleSheet.styleSheet !== "undefined") {
newStyleSheet.styleSheet.cssText = csstext_to_add;
}
else {
newStyleSheet.appendChild(document.createTextNode(csstext_to_add));
newStyleSheet.innerHTML=csstext_to_add;
}
}
catch(e){}
}
It is also possible to add that class as option into a dropdown (what takes some effort).
Thariama's answer was perfect. I'm using the tinyMCE jQuery connector for some of my pages and I have multiple instances of tinyMCE on the page. I made some modifications, but essentially its the same thing. I've created a text area field on the page that people can provide their own CSS. Also, I needed to change some CSS rules on the fly...
// function to change tinyMCE css on the fly
function checkCustomCSS() {
var $css = $('#page_css'),
newcss;
if ($css.val().length > 0) {
// since front end, we are wrapping their HTML in a wrapper and
// the tinyMCE edit iFrame is just using <body>, we need to change
// some rules so they can see the changes
newcss = $css.val().replace('#content_wrapper', 'body');
// loop through each tinyMCE editor and apply the code changes
// You could check the editor.id to make sure that the correct
// editor gets the appropriate changes.
$.each(tinyMCE.editors, function() {
var $this = $(this),
editorID = $this[0].id,
$ifrm = $('#' + editorID+ '_ifr'),
cwin, head, sheet;
if ($ifrm.length > 0 /* && editorID === 'OUR_EDITOR_ID_NAME' */) {
cwin = $ifrm[0].contentWindow;
head = cwin.document.getElementsByTagName("head");
if (!head.length) {
return;
}
sheet = cwin.document.createElement("style");
sheet.type = "text/css";
head[0].appendChild(sheet);
try {
if (typeof sheet.styleSheet !== "undefined") {
sheet.styleSheet.cssText = newcss;
} else {
sheet.appendChild(cwin.document.createTextNode(newcss));
sheet.innerHTML = newcss;
}
} catch (e) {}
}
});
}
}
Then in the tinyMCE init call I added and onInit call to setup changes to the #page_css , like this:
oninit: function() {
$('#page_css').on('change', function() {
checkCustomCSS();
});
}
Works like a charm.