AngularJS- how to return multiple buttons? Return a promise that resolves to an array? - angularjs

I have an AngularJS application, and on one of the pages, there is a button that opens a dialog allowing the user to configure a particular widget. That widget displays a table, and I have recently added the functionality to allow the user to add a single button to a given table cell. This functionality all currently works correctly.
I am now trying to extend the functionality, to allow a user to add multiple buttons to a single table cell.
The way that the user currently adds a button to a cell, is by typing the address of the page that they want to add the button for into a text field on the dialog- the page that they type must be preceded with a : character. However, that text field is also used to display variable values, so for example, to display a variable value, they would type the name of the variable, i.e. myVariable, but to add a button which would link to the Home page, they would type :pages/home.
The value that they type into the text field is converted to the appropriate object for display in the table by a function called toItemObj(item).
This function is defined with:
function toItemObj() {
...
switch(widgetObj.name) {
case 'table':
...
widgetObj.table = angular.copy($scope.widget.table);
...
var multiBtns = ";";
var btnArray = [];
var btnTmplArray = [];
var pageTitle;
...
angular.forEach(widgetObj.table.rows, function(row) {
if(row.length > 0) {
reducedRows.push(row.map(
function(vrbl, idx) {
...
if(widgetObj.table.header[idx].startsWith(":")) {
if(vrbl.vrbl.includes(multiBtns)) {
btnArray = vrbl.vrbl.split(multiBtns);
if(btnArray[0].startsWith(":")) {
brnArray[0] = btnArray[0].split(":")[1];
}
var btnArrayBtn = 0;
angular.forEach(btnArray, function() {
var btnTmpl = $compile(
'<a href="javascript:;" ng-click="goToPage(target)"' +
'class="btn btn-xs btn-brand">Go to page</a>')
toVrblItemObj(btnArray[btnArrayBtn]);
compiledBtns.push(btnTmpl);
console.log("compiledBtns: ", compiledBtns);
})
} else {
console.log("Vrbl does not include multiBtns: ", vrbl.vrbl);
}
} else {
console.log("Value of widgetObj.table.headers[idx]: ", widgetObj.table.headers[idx]);
}
console.log("Returning vrbl.vrbl: ", vrbl.vrbl);
return vrbl.vrbl;
}
));
}
});
widgetObj.table.rows = reducedRows;
...
break;
In the if(vrbl.vrbl.includes(multiBtns)) { block, I am checking whether the value given to the button by the user contains a ; character, and if it does, I am splitting that string on the ; character, and compiling a button for each of the split elements, then pushing that compiled button to a list called compiledBtns.
The problem I'm having is with returning multiple buttons when they've been compiled...
When I load the page with this code as it is above, and try adding a button to a table cell, if I just add a single button to the cell (i.e. type something like :pages/page1 into the field in the dialog, then that button is displayed correctly. But, if I try to add more than one button to a table cell, (i.e. type something like :pages/page1;pages/page2 into the field in the dialog), then the compiled buttons are not displayed, and I just get the text that I actually typed:
I thought that this might be because my return statement is only returning the single vrbl.vrbl element (i.e. a single button), so I tried changing it to:
if(widgetObj.table.headers[idx].startsWith(":")) {
console.log("column heading starts with : - returning compiledBtns- ", compiledBtns);
return compiledBtns;
} else {
return tag.tag;
}
i.e. if the column is a 'buttons' column (the heading starts with a : character, then it should return the list of compiled buttons, but if not, then it should return a variable.
Doing this caused the table to be displayed as:
i.e. it stops even the cells with only one button from showing the button...
The console is showing me that the buttons are being compiled:
compiledBtns: (2) [ƒ, ƒ]0: ƒ publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn)1: ƒ publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn)length: 2__proto__: Array(0)
ctrl.js:467 Returning tag.tag: :pages/auth;pages/userpage1
but for some reason, they're just not being shown...
Can anyone explain what I'm doing wrong here? How can I get my function to return and display multiple buttons in the same table cell?
Edit
I've just noticed that when only entering one button in a table cell, the button is only displayed/ rendered as a button when you select it from the 'autocomplete' list that's displayed when you start typing- i.e. if you type out the page address in full, and click 'Preview' on the dialog, the button is not displayed in the table- it just shows the textual address that you typed.
When trying to add multiple buttons to the table cell, obviously the autocomplete doesn't recognise any pages with the address of two combined page address (separated with a ;), so you can't select more than one page from the drop down at a time.
How would I enable the selection of multiple items from the autocomplete drop down? The autocomplete functionality is defined in the HTML:
<div class="repeat-item-animation" data-ng-repeat="row in widget.table.rows">
<div class="row">
<div class="col-sm-12 input-addon-btn widget-picker-table-row-input">
<tags-input min-length="1"
um-max-tags-strict
key-property="tag"
data-ng-model="row"
display-property="tag"
template="tagItem.html"
replace-spaces-with-dashes="false"
on-tag-adding="onAddingTagItem($tag)"
on-tag-added="warning.rows = undefined"
um-tags-input-warning="{{warning.rows}}"
max-tags="{{widget.table.headers.length}}"
placeholder="Start typing a tag name or some text">
<auto-complete min-length="1"
load-on-focus="true"
load-on-empty="true"
display-property="tag"
select-first-match="false"
template="autocomplete.html"
source="autocompleteTagsFilter($query)">
</auto-complete>
</tags-input>
<a href data-ng-click="widget.table.rows.splice($index, 1)"
class="btn-icon btn-icon-sm btn-config btn-danger">
<span class="glyphicon glyphicon-minus-sign"></span>
</a>
</div>
</div>
I've Google'd a bit, but can't find any properties for the above <auto-complete> tag that would appear to enable selection of more than one element... Anyone have any suggestions?
Edit
So it seems that the auto-complete feature is actually a part of the implementation of ngTagsInput, which it seems the application is using to provide this functionality. From reading its documentation, it seems that the source parameter is the one I want to be using to return an array of strings (i.e. the links for multiple pages that I want to add buttons for). In the 'Parameters' table under the 'Usage' section of the autoComplete directive in ngTagsInput, the description of source is:
Expression to evaluate upon changing the input content. The input value is available as $query. The result of the expression must be a promise that eventually resolves to an array of strings.
In my application, the source attribute is currently set to the function autocompleteTagsFilter($query), which is defined with:
$scope.autocompleteTagsFilter = function(query) {
// Use last valid query keyword instead if input keyword is undefined
// When the first character of the tag that the user enters is a ':',
// the drop down should display a list of available user pages, not tags.
// Put the 'if(!query && lastTagQryKw)' inside the 'else', so that this
// is run whenever the entered tag name doesn't start with ':'
if (query.startsWith(":")) {
console.log("query starts with ':' ", query);
/*Check whether string has a ';' & split it if it does */
if(query.includes(";")) {
var strings = query.split(";");
console.log("strings[0]: ", strings[0]);
console.log("strings[1]: ", strings[1]);
var strings0 = strings[0].split(":")[1];
// console.log("strings0: ", strings0);
var strings1 = strings[1].split(":")[1];
// console.log("strings1: ", strings1);
/*Now need to pass each string to 'toTagItemObj(), to make it a tag item object', so that it can be displayed as a button */
angular.forEach(strings, function(string, btnString) {
// console.log("angular.forEach - value of string: ", string);
// console.log("angular.forEach - value of btnString: ", btnString);
toTagItemObj(string);
})
}
// Split the 'query' search string on ':', to use only the string
var buttonsQuery = query.substring(1);
if (!buttonsQuery && lastTagQryKw) {
buttonsQuery = lastTagQryKw;
}
/*check whether the buttonsQuery variable contains a ';' - if it does, split it */
if(buttonsQuery.includes(';')) {
console.log("buttonsQuery includes ; - ", buttonsQuery);
var btnsToDisplay = buttonsQuery.split(";");
console.log("btnsToDisplay: ", btnsToDisplay);
}
// Declare variables to be used in 'for' loop
var userPages = pagesPresets;
var page;
var result = [];
// 'For' loop should iterate through the list of user pages,
// and remove path, so that only the page name is shown
for (page in userPages) {
page = userPages[page];
// If the page key starts with 'buttonsQuery', and its length
// is greater than 6 (i.e. just 'pages/' shouldn't be displayed)
// add the page to the list of pages to be displayed.
if (page.key.startsWith(buttonsQuery) && page.key.length > 6) {
result.push(page.key);
}
};
if (result.length > 0) {
lastTagQryKw = query;
}
// Return the list of pages that match what the user has typed
return result;
// Otherwise, if the user types something that does not start with ':',
//then it should be a tag- search for tags that match this term
} else {
if (!query && lastTagQryKw) {
query = lastTagQryKw;
}
var result = Object.keys(fxTag.getTags()).filter(function(name) {
return name.indexOf(query.toLowerCase()) !== -1;
});
if (result.length > 0) {
lastTagQryKw = query;
}
return result;
}
};
As it currently stands, this autocompleteTagsFilter() function is returning an array of strings in its return statement. The array of strings is what's shown in the list of auto-complete options available, but the button is only actually created when selecting one of them.
According to the documentation, I need this to return a promise which resolves to an array of strings, but I'm not sure how I'd do this... Can anyone suggest a way I might make this function return a promise that resolves to an array of strings?
Edit
The template code for the markup/ cell rendering as it currently stands is:
angular.module('app.widget').run(function($templateCache) {
// Add basic suggestion template of autocomplete
$templateCache.put(
'autocomplete.html',
'<div class="pull-left autocomplete-text autocomplete-full-text">' +
'<span ng-bind-html="$highlight($getDisplayText())"></span></div>'
);
$templateCache.put('tagItem.html',
'<div data-ng-click="data.nounit = data.isTag && !data.nounit" ' +
'class="glyphicon-clickable" um-tag-item-disabled>' +
'<span class="tag-item-icon glyphicon glyphicon-tag" ' +
'data-ng-show="data.isTag"></span><span class="tag-item-text">' +
'{{$getDisplayText()}}</span><span class="tag-item-unit" ' +
'data-ng-hide="!data.units || data.nounit">({{data.units}})</span>' +
'<a class="remove-button" ng-click="$removeTag()">' +
String.fromCharCode(215) + '</a></div>'
);
}).controller(...){
I tried changing this to use an ng-if, so that it would show a different template for when buttons should be displayed for multiple pages, but this gives me an error in the console that says:
Error: [ng:areq] Argument 'WidgetPickerCtrl' is not a function, got undefined
I got this error after changing the template to:
angular.module('ultimetric.widget').run(function($templateCache) {
// Add basic suggestion template of autocomplete
$templateCache.put(
'autocomplete.html',
'<div class="pull-left autocomplete-text autocomplete-full-text">' +
'<span ng-bind-html="$highlight($getDisplayText())"></span></div>'
);
$templateCache.put('tagItem.html',
'<ng-if="data.isPages">' +
'<div data-ng-click="data.toPageBtn = data.isPages && !data.toPageBtn" ' +
'class="glyphicon-clickable" um-tag-item-disabled>' +
'<span class="tag-item-icon glyphicon glyphicon-tag" ' +
'data-ng-show="data.isBtn"></span><span class="tag-item-text">' +
'{{$getDisplayText()}}</span><span class="tag-item-text">' +
'data-ng-hide="!data.units || data.toPageBtn">({{data.unnits}})</span>' +
String.fromCharCode(215) + '</a></div>'
'<div data-ng-click="data.nounit = data.isTag && !data.nounit" ' +
'class="glyphicon-clickable" um-tag-item-disabled>' +
'<span class="tag-item-icon glyphicon glyphicon-tag" ' +
'data-ng-show="data.isTag"></span><span class="tag-item-text">' +
'{{$getDisplayText()}}</span><span class="tag-item-unit" ' +
'data-ng-hide="!data.units || data.nounit">({{data.units}})</span>' +
'<a class="remove-button" ng-click="$removeTag()">' +
String.fromCharCode(215) + '</a></div>'
);
}).controller(...){
Any ideas how I can get the autocomplete to show multiple buttons at the same time?

Related

Show textareas values in parent component as a list & not showing tabs on page load

I created a form with Angular Material, inside the form I have a mat-radio-group for radio buttons, and mat-tab-group
For tabs, each one of this tabs have an input. I have two questions.
1 - The tab group doesn’t show when the page loads, I have to click in a radio button to see the tabs, I don’t know why this is happening.
2 - I am getting the values from the textareas and I am showing them in the parent component, When I write a new value in the text area, this one replace the previous value, but I need all the values from the different textareas.
1st issue - not able to see tabs on reload/load
actually, as per the code, we are getting console errors, and because of that, we are not able to see tabs. it is breaking at the radio group itself.
console errors
Fix for the above issue - remove the formControlName="titleAction" for each radio button.
2nd issue - not getting all tabs data in a parent from child comp
so... here you are not sending all tabs data. that is the actual issue.
I did a few changes in child and parent as per your expected result.
sending array from child like below
submit() {
this.updateDataEvent.emit({formdata:this.form.getRawValue(), tabs: this.tabs});
}
and changed code in the parent component while updating a few global variables
updateData(selection: any): void {
this.titleScreening =
selection.formdata.titleAction === 'change'
? selection.formdata.titleText
: 'Original Title';
//Empty Inputs
if (this.titleScreening === '') {
this.titleScreening = 'Original Title';
}
this.divTag = this.divTag2 = ''
selection.tabs.forEach((x) => {
// this.name = x.fullName;
var appendElement = '<li> <span> Name </span>' + x.name + '</li>';
this.divTag = this.divTag + appendElement;
// Show in parent component
var appendElement2 = '<li> Value: ' + x.value + '</li>';
this.divTag2 = this.divTag2 + appendElement2;
});
// selection.fullName === ''
// ? (this.divTag = '')
// : (this.divTag = appendElement);
}
stackblitz

CQ/AEM extjs get selection dropdown box text, and get page path

Say I have a component with dialog drop to parsys on a content page /content/phonegap/ss/en_gb/login/home/test1/jcr:content/par/productimage
now inside the dialog i have something like
I wish to get $PATH append to URL and send selected device e.g the text 'Device ID:HTC_10_GOLD', to servlet in this dialog listener extjs:
<deviceAndColour
jcr:primaryType="cq:Widget"
allowBlank="{Boolean}false"
fieldLabel="Device and Colour"
name="./deviceAndColour"
options="/bin/reference/data/device.devices.json$PATH"
type="select"
xtype="selection">
<listeners
jcr:primaryType="nt:unstructured"
selectionchanged="function(pathfield) {
var selected = this.findParentByType('form').find('name', './deviceAndColour')[0].getText();
console.log( this.findParentByType('form').find('name', './deviceAndColour')[0].getText());
$.getJSON('/bin/reference/data/device/availablecolour.availablecolour.json$PATH?selectedDevice=' + selected + '&colour=red', function(jsonData){
selectBox.setOptions(jsonData);
});
}" />
</deviceAndColour>
So bascially, the console.log( this.findParentByType('form').find('name', './deviceAndColour')[0].getText()); is not working as I expected, neither the $PATH inside the dialog listener js, it does not retrieve the path at all.
Apart from above attempt, I know var selected = this.findParentByType('form').find('name', './deviceAndColour')[0].getValue(); this will get the value asscociated with the select correctly, but I do not need value, I just wish to getText(), and get the current $PATH in extjs.
another questions, you may noticed $.getJSON('/bin/reference/data/device/availablecolour.availablecolour.json$PATH?selectedDevice=' + selected + '&colour=red'
how do I skip the & in this listner, as if i use & directly, project won't even build. there must be someway to skip & and let extjs treat as part of the string to send request
Anyone expereinced this before? please suggest with code example.
Thanks
Getting the text of the option selected:
function(field,value){
for(var i = 0; i < field.options.length; i++) {
if(field.options[i].value === value) {
console.log('Selected: ' + field.options[i].text);
break;
}
}
}
Getting the path to the resource being edited:
function(field,value){
console.log("Resource:" + field.findParentByType("dialog").path);
}
Documentation: https://docs.adobe.com/docs/en/cq/5-6/widgets-api/index.html?class=CQ.form.Selection
UPDATE
Please try the following code adapted to your scenario (I also refactored the code to make use of params when providing query parameters. There is no reason why this shouldn't work.
function(field, value) {
var selected = '';
var path = field.findParentByType("dialog").path;
// get text of option selected
for(var i = 0; i < field.options.length; i++) {
if(field.options[i].value === value) {
selected = field.options[i].text;
break;
}
}
var params = {
selectedDevice: selected,
colour: 'red'
}
$.getJSON('/bin/reference/data/device/availablecolour.availablecolour.json'+path, params, function(jsonData){
// FIXME: how are you getting the "selectBox" reference?
selectBox.setOptions(jsonData);
});
}

AngularJS rest call, followed by opening new window with body of response

I have a table dynamically populated, the last column being the result of a function like this:
function actionsHtml(data) {
$scope.link="/myRestTarget?restParam=" + data.value;
return '<a target="_new" ng-href="{{link}}" ng-click="$event.preventDefault();linkClicked(link);">Explorer <i class="fa fa-binoculars"/></a>'
}
when the user clicks on a the column button, a rest call is initiated; which creates a doc; returns the id of this doc; appends the id to the uri and then opens it in a new window.
$scope.linkClicked = function(link){
$http.get(link.toString()).then(function (result) {
var newLink = 'http://newUrl:8080/' + result.data.body
$window.open(newLink, '_blank')
});
};
The rest + window open works exactly as intended. However the $scope.link value is always the same (and always the column of the last row in the table). Now I get why this is the case - link is set by each row in turn. However I am unable to bind a value to the cell in question - unless I use $scope.link the value of $scope.linkClicked is always undefined....
Found the answer - kind of obvious really :S
function actionsHtml(data) {
var link="/myRestTarget?restParam=" + data.value;
return '<a target="_new" href="' + link + '" ng-click="$event.preventDefault();linkClicked(link);">Explorer <i class="fa fa-binoculars"/></a>'
}

AngularJS : why after loading more data filter stop working?

there is one filter functionality in my demo I will explain my problem I have one table in which i use infinite scroll is implemented In other words when user moves to bottom it load more data.There is search input field in top .Using this I am able to filter item in table .but I don't know why it is not working
When you search "ubs" and "ing" first time .it works perfectly .But when you load more data other words when user scroll to bottom and load more data the again it try to filter "ubs" and "ing" it not give any result why ?
<label class="item item-input">
<img src="https://dl.dropboxusercontent.com/s/n2s5u9eifp3y2rz/search_icon.png?dl=0">
<input type="text" placeholder="Search" ng-model="query">
</label>
secondly Actually I am implementing infinite scroll so only 100 element display .can we search element from 2000 (which I am getting from service )and display data the search result ?
Update :
Here's a Plunker with everything working together. I have separated all of the pieces into individual JS files, as it was getting unruly:
Plunker
Search
The built in filter will only return results from the current view data that the ng-repeat is displaying. Because you're not loading all of the data into the view at once, you'll have to create your own search functionality.
In the demo, click the search icon to show the search box, then type your search value and press the ENTER key or click the search button to return the results.
Since you want to check whether the user pressed ENTER you have to pass both the event and the querystring to the function, so you can check for the enter keycode. The function should also run when someone clicks or taps the search button. I set ng-model="query" on the input, so query is the reference in the view. Therefore, you'll add ng-click="searchInvoices($event, query)" to your search button, and ng-keyup="searchInvoices($event, query)" to the input. And, finally, to make it easy to clear the input field, add a button that displays when the input is not empty with ng-show="query" and attach a click event with ng-click="query=null; resetGrid()".
Add the searchInvoices function to your controller. It will only run the search if either the query is empty (because you need to reset the view if the person uses the backspace key to empty the input) OR if the user pressed ENTER OR if the event was a click event in case the user clicks the search button. The inner if statement, prevents the search from running if the query is empty and just resets the view. If the query is not empty, against the total dataset and builds an array of matching results, which is used to update the view.
The last line sets the scroll position to the top of the scrollview container. This makes sure that the user sees the results without having to click somewhere in the scrollview container. Make sure you inject the $ionicScrollDelegate into your controller for this to work and set delegate-handle="invoicegrid" on your ion-scroll directive.
$scope.searchInvoices = function(evt, queryval) {
if (queryval.length === 0 || evt.keyCode === 13 || evt.type === 'click') {
if (queryval.length === 0) {
$scope.invoice_records = $scope.total_invoice_records;
} else {
var recordset = $scope.total_invoice_records;
results = [];
var recordsetLength = recordset.length;
var searchVal = queryval.toLowerCase();
var i, j;
for (i = 0; i < recordsetLength; i++) {
var record = recordset[i].columns;
for (j = 0; j < record.length; j++) {
var invoice = record[j].value.toLowerCase();
if (invoice.indexOf(searchVal) >= 0) {
results.push(recordset[i]);
}
}
}
$scope.invoice_records = results;
$ionicScrollDelegate.$getByHandle('invoicegrid').scrollTop();
}
}
};
Lastly, you need to modify the loadMore() function that is used by the infinite scroll directive, so that it doesn't try to load additional data when scrolling through the search results. To do this, you can just pass the query into loadMore on the directive like: on-infinite="loadMore(query)", then in your function, you can just run the broadcast event when the query exists. Also, removing the ngIf will ensure that the list remains dynamic.
$scope.loadMore = function(query) {
if (query || counter >= $scope.total_invoice_records.length) {
$scope.$broadcast('scroll.infiniteScrollComplete');
} else {
$scope.counter = $scope.counter + showitems;
$scope.$broadcast('scroll.infiniteScrollComplete');
}
};
You used filter in wrong way inside ng-repeat like ng-repeat="column in invoice_records | filter:query" instead of ng-repeat="column in invoice_records | query"
<div class="row" ng-repeat="column in invoice_records |filter:query">
<div class="col col-center brd collapse-sm" ng-repeat="field in column.columns" ng-show="data[$index].checked && data[$index].fieldNameOrPath===field.fieldNameOrPath">{{field.value}}</div>
<div class="col col-10 text-center brd collapse-sm"></div>
</div>
Demo Plunkr

how to implement angularjs typeahead when user types '#' only

When user types '#' in the textarea, i want to display the some values as suggestions.
I used typeahead directive, but it is showing the suggestions for the macthing value entered in the textarea. I want to show only when user types '#'.
Please sugeest me.
We had to create such a typeahead for '##'.
Maybe use Bootstrap UI Typeahead. You just need to create your own filter and check if the input contains your '#'.
If you want to completely create it by yourself, than you need a watcher on the ngModel variable. If user does input stg, check for the # and show/hide your typeahead.
The html must be $compiled with the scope and injected after your textfield.
var template = angular.element(
'<ul class="dropdown-menu typeahead">' +
'<li data-ng-repeat="match in arrMatches = (matches | filter:matchTypeAheadFilter)" ng-class="{\'active\':selectedMatch==match}" >' +
'<a data-ng-bind="match" data-ng-click="injectMatch(match);"></a>' +
'</li >' +
'</ul>');
$compile(template.contents())(scope);
$(element).after(template);
Now this template is the typeahead container.
Every li represents a match.
scope.$watch("ngModel", function (newValue,oldValue)
{
if(newValue.indexOf('#') > -1)
scope.openTypeAhead(newValue);
});
In the openTypeAhead you need to logic to show the container, apply click + key events.
scope.openTypeAhead = function(inputValue){
var firstTimeOpen = $(template).css("display") == "none";
if(firstTimeOpen)
{
$(element).keydown(keyEvents);
$(document).click(onClick);
}
$(template).show(); //show typeahead
scope.matches = //load your matches
}else{
scope.hideTypeAhead();
}
};
I'd suggest to your the framework. Because this is not simply done.

Resources