Framework7 virtual list searchAll function not being used - javascript

I've got a PhoneGap application in development where I'm trying to use framework7's search bar to filter out my virtual list of products.
Current functionality is that the list works fine, but the search bar only searches through the rendered elements rather than the whole virtual list.
I've gone through framework7's documentation on getting their virtual list and searchbars to work together, but as far as I can tell the searchbar in my code completely ignores the virtual lists searchAll function which I put in. I can have searchAll() return anything and it makes no difference to the current functionality.
var listObject = {
items: selectProd,
template: '<li class="item-content"><div class="item-inner"><div data-value="{{model_id}}" class="item-title list-title">{{internal_descriptn}}</div></div></li></script>',
searchAll: function (query, items) {
var foundItems = [];
for (var i = 0; i < items.length; i++) {
// Check if title contains query string
if (items[i].title.indexOf(query.trim()) >= 0) foundItems.push(i);
}
// Return array with indexes of matched items
return foundItems;
}
};
console.log(listObject);
var virtualList = myApp.virtualList('#product-list', listObject);
var mySearchbar = myApp.searchbar('.searchbar', {
searchList: '.list-block-search',
searchIn: '.list-title'
});
I feel like the only thing I could be missing is some way to put the virtualList into the searchbar as an attribute or similar to link them, it seems strange for me to expect them to just work together like magic. Yet that seems to be what the documentation suggests it does (not in my case apparently or it would work). Thanks for any help.

Solved it by looking at an example on their github. At first glance everything is the same, so I copy it over and slowly change it back to include my data to see where the problem occurs. For some godamn reason, you need to use the class virtual-list to identify the parent containing your list. Then specify that class for your virtual list and for your search bar. Using a different name instead won't work. Very frustrating that this isn't mentioned anywhere at all in documentation.

Related

Filtering smart-table on transformed data

Apologies in advance, I am not a very experienced JS programmer, and even less so with AngularJS, but am trying to make some improvements on a legacy codebase that is using Angular 1.5.9 and Smart Table to display information from a database.
I've read all about st-search and st-safe-src vs. st-table, etc., but I am having trouble filtering on my table, since there are transformations happening on the underlying data before it gets displayed. The ng-repeat variable in my case is transaction, which has various fields to hold information for that transaction, such as payee, which holds a UUID pointing to another database document. In the app, we display the name of that payee using a function from another controller (dbCtrl.getPayeeName()), but the underlying data is the UUID. Thus, when attempting to filter with Smart Table, it does not filter on the displayed names, and only works when entering the UUID into the filter field.
A small example (with lots of the intervening bits removed, but hopefully enough to demonstrate my confusion):
<div class="account"
st-table="displayedTransactions"
st-safe-src="transactions"
disable-ng-animate>
...
<div><input st-search="payee" placeholder="search for payee" class="input-sm form-control" type="search"/></div>
...
<div ng-repeat="transaction in displayedTransactions track by transaction.id">
...
<div class="account__td" transaction-field-focus-name="payee">
{{dbCtrl.getPayeeName(transaction.payee)}}
</div>
...
</div>
Is there a relatively simple way to get the filtering to work for a situation like this where the displayed data is different than the underlying data? From what I'm reading in the documentation, it sounds like this might require some sort of custom plugin, which sounds like more work, but I could maybe figure out. I just wanted to see if I'm missing something obvious before heading down that route.
Circling back on this, I was able to accomplish what I needed using the st-set-filter attribute as described in the Strict mode filtering section of the documentation, as well as this helpful answer from laurent back in 2014.
Essentially, I added st-set-filter="transactionFilters" to my table in my html template, as well as input tags with st-search="prop_to_search" attributes. Then in my applications module (I put this in one of the controllers, not sure if that's totally correct) I defined a filter such as below. expression gets passed into this code as an object with string values for whatever you typed in, so if you had three search fields, you'd get an object like:
{
"prop_to_search1": "value1",
"prop_to_search2": "value2",
"prop_to_search3": "value3"
}
In the filter function, I wrote an if block for each property that could come in, and then do my custom transformations and pattern matching there. This way, I have full control over the eventual matching, and rather than searching on the UUID, I can do something like $rootScope.dbCtrl.getPayeeName(uuidToSearch) instead. This is all acceptably performant in my use case, but I could probably cache those database lookups as a potential optimization.
angular.module('myApp').filter('transactionFilters', function($rootScope, $filter){
return function(array, expression){
// console.log(`expression is: ${JSON.stringify(expression, null, 4)}`)
return array.filter(function(val, index){
// in this function's context, `expression` is an object with
// the active filters entered in each field; `val` is the data
// representation of each row of the table
// if this function returns true, the row will match and smart-table
// will show it, otherwise it will be hidden
// define matches all at once, check them all, then return
// a big logical AND on all of them
let accountMatch = true;
let payeeMatch = true;
let categoryMatch = true;
if (expression.account) {
uuidToSearch = val.account // this is the account UUID
strToSearch = $rootScope.dbCtrl.getAccountName(uuidToSearch).toLowerCase(); // convert to an account name (we could memoize this to improve performance)
if (strToSearch) {
// if the account had a name (it always should, but catch in case)
// then check if the row's account contains the text entered in the filter field
accountMatch = strToSearch.includes(expression.account.toLowerCase());
} else {
accountMatch = false;
}
}
if (expression.payee){
if (val.payee) {
uuidToSearch = val.payee
strToSearch = $rootScope.dbCtrl.getPayeeName(uuidToSearch).toLowerCase();
}
if (strToSearch) {
payeeMatch = strToSearch.includes(expression.payee.toLowerCase());
} else {
payeeMatch = false;
}
}
if (expression.category) {
if (val.category) {
strToSearch = $rootScope.dbCtrl.getCategoryName(val.category, val.date).toLowerCase()
categoryMatch = strToSearch.includes(expression.category.toLowerCase())
} else {
categoryMatch = false;
}
}
return (
accountMatch &&
payeeMatch &&
categoryMatch
)
})
}
});

NodeJS Puppeteer Get InnerText of Child Elements from XPath

I have a project to scrape the products purchased by certain customers from an internal CRM. This CRM uses a lot of dynamically loaded tiles, so there are not many consistent class names (many have an ID randomly appending at each page load), and there are also many different reports/elements on a page with the same class name, so I can't query the whole page for an element selector.
I have identified the "parent" element that I want via xpath. I then want to drill down and get the innerText of only the children who match the query selector (most threads I see have people doing the query selector on the whole page, this will get results from menus I don't want).
I can do this in regular Javascript in the console of the browser, I just can't figure out how to do it in Node/Puppeteer. Here's what I have so far:
//Getting xpath of the "box" that contains all of the product tiles that a customer has
const productsBox = await page.$x("/html/body/blah/blah/blah");
This is where it breaks down. I'm not super familiar with some of the syntax or understanding Puppeteer's documentation, but I've tried a few different methods (I'm also not comfortable enough with functions to use the => format. The Puppeteer documentation has an example of what I'm trying to do, but I tried with the same structure and it also returned nothing):
//Tried using the elementHandle.$$eval approach on the zero index of my xpath results,
//but doesn't return anything when I console.log(productsList)
const productsList = await productsBox[0].$$eval('.title-heading', function parseAndText (products) {
productsList=[];
for (i=0; i<products.length; i++) {
productsList.push(products[i].innerText.trim());
}
return productsList;
}
);
//Tried doing the page.$$eval approach with selector, passing in the zero index of my xpath
const productsList = await page.$$eval('.title-heading', function parseAndText (products) {
productsList=[];
for (i=0; i<products.length; i++) {
productsList.push(products[i].innerText.trim());
}
return productsList;
}, productsBox[0]
//Tried the page.evaluate and then page.evaluateHandle approach on the zero index of my xpath,
//doing the query selection inside the evaluation and then doing something with that.
let productsList= await page.evaluateHandle(function parseAndText(productsBoxZero) {
productsInnerList = productsBoxZero.querySelectorAll(".title-heading");
productsList=[];
for (i=0; i<productsInnerList.length; i++) {
productsList.push(productsInnerList[i].innerText.trim());
//Threw a console log here to see if it does anything,
//But nothing is logged
console.log("Pushed product " + i + " into the product list");
}
return productsList;
}, productsBox[0]);
In terms of output, I've console logged some of the variables and I get this:
productsBox is JSHandle#node
productsBox[0] is JSHandle#node
productList is
For comparison, I was doing this in parallel via Javascript in the console to make sure I'm stepping through the logic correctly and I get what I expect:
>productsBox=$x("/html/body/blah/blah/blah");
>productsInnerList=productsBox[0].querySelectorAll(".title-heading");
>productsInnerList.length;
//2, and this customer has 2 products
>productsList=[];
>for (i=0; i<productsInnerList.length; i++) {
productsList.push(productsInnerList[i].innerText.trim());
};
>console.log(productsList)
>["Product 1", "Product 2"]
Thanks for reading this far and I appreciate your help!
[Edit]
For some additional research, I have tried to use page.evaluateHandle and tried to log my variables so far:
productsBox is JSHandle#node
productsBox[0] is JSHandle#node
productList is JSHandle#array
Which is progress. I tried to do:
let productsText=await productsList.jsonValue();
But when I try to output I get nothing:
await console.log("productsText is " + productsText);
productsBox is JSHandle#node
productsBox[0] is JSHandle#node
productList is JSHandle#array
productsText is
I'd suggest reading the docs carefully before trying every function.
$$eval evaluates on the selector and passing the element is pointless in this case. evaluateHandle is for returning in-page elements, since you're returning an array of text and it's serializable, you don't need it. All you need is to pass the element to page.evaluate or do everything in puppeteer context.
To be able to see in-page console.log you need to:
page.on('console', msg => console.log(msg.text()));
Using page.evaluate
let productsList= await page.evaluate((element) => {
const productsInnerList = element.querySelectorAll(".title-heading");
const productsList=[];
for (const el of productsInnerList) {
productsList.push(el.innerText.trim());
console.log("Pushed product " + el.innerText.trim() + " into the product list");
}
return productsList;
}, productsBox[0]);
Using elementHandle.$$
const productList = [];
const productsInnerList = await productsBox[0].$$('.title-heading');
for (const element of productsInnerList){
const innerText = await (await element.getProperty('innerText')).jsonValue();
productList.push(innerText);
}
Based on #mbit's answer I was able to get it to work. I first tested on another site that was similar in structure to mine. Copied code over to my original site and it still wasn't working, only got a null output. Turns out that while I had an await page.$x(full/xpath) for the parent element, the child elements that contained the innerText still hadn't loaded. So I did two things:
1) Added another await page.$x(full/xpath) for the first element in the list that was one of my targets
2) Implemented the page.evaluate approach provided by mbit.
2a) Explicitly wrote out the function (still wrapping head around the => structure)
Final code below (some variable names changed as a result of testing):
let productsTextList= await page.evaluate(function list(list) {
const productsInnerList = list.querySelectorAll(".title-heading");
productsTextList =[];
for (n=0; n<productsInnerList.length; n++) {
product=productsInnerList[n].innerText.trim();
productsTextList.push(product);
}
return productsTextList;
}, productsBox[0]);
console.log(productsTextList);
I chose the page.evaluate approach because it more closely matched what I was doing in the browser console, so easy to test with. The trick with the elementHandle.$$ approach was, as mbit mentioned, using await element.getProperty('innerText') rather than .innerText. Throughout troubleshooting and learning, I also stumbled across this thread on GitHub which also talks about how to extract it (same as mbit's approach above). For anyone running into similar issues you aren't alone!

Angular2+ using .getElementsByClassName() returns empty selection though existent

I have a fairly simple question maybe, but I can't find an answer to it. I also searched previous questions but it seems not to work....
I have on my template in angular a large amount of text inside a div and some parts are wrapped with a span and those have the class highlight. Now I simply want to select them all when I press on a button. I need all the spans and do sth with them later.
I tried:
const elm = (this.el as any).getElementsByClassName('.highlight');
console.log('elmens: ', elm);
for (let i = 0; i < elm.length; i++) {
console.log('HTML ELM: ', elm[i]);
}
Also with:
const elm = document.getElementsByClassName('.highlight');
This both only returns me an empty selection, although they are existent. I even tried it on the browser console. I know I could use jQuery maybe, but I don't want to include it for such simple tasks.
Maybe you know what I am doing wrong???
I am using:
Angular: 7.0.0
Angular-cli: 7.0.2
Typescript: 3.1.3
I would look to avoid using this technique within the angular framework as it is not recommended to interact with the DOM in this way. They have appropriate alternatives. Interacting with the DOM can introduce Performance implications and is bad practice.
Instead you could look into using #viewchildren for this problem.
Docs here
Which would suggest each element you want to get, in your case the spans
<span #myNewId>text</span>
<span #myNewId>text snippet 2</span>
Could be accessed in the controller with the following
#ViewChildren('myNewId')public mySpans: ElementRef;
This should give you all the same functionality without having to interact directly with the DOM. mySpans will then be an array of the spans marked with the #myNewID.
This should also allow you access your elements in regards to your question, as they are bound the variables in the class.
I would use:
const elements= (<HTMLCollection>document.getElementsByClassName('your_class_name_without_dot'));
console.log(elements)
Maybe your child views are not initialized and you try to play with DOM before elements are rendered to the browser. try to use Angular hooks and then call you code. Like
ngAfterViewChecked() {
...
const elm = (this.el as any).getElementsByClassName('.highlight');
console.log('elmens: ', elm);
for (let i = 0; i < elm.length; i++) {
console.log('HTML ELM: ', elm[i]);
}
...
}

Testing tab navigation order

In one of our tests, we need to make sure that the tab keyboard navigation inside a form is performed in the correct order.
Question: What is the conventional way to check the tab navigation order with protractor?
Currently we are solving it by repeating the following step for as many input fields existing in a form (code below):
check the ID of the currently focused element (using getId())
send TAB key to the currently focused element
Here is the example spec:
it("should navigate with tab correctly", function () {
var regCodePage = new RegCodePage();
browser.wait(protractor.ExpectedConditions.visibilityOf(regCodePage.title), 10000);
// registration code field has focus by default
expect(regCodePage.registrationCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
// focus moved to Remember Registration Code
regCodePage.registrationCode.sendKeys(protractor.Key.TAB);
expect(regCodePage.rememberRegistrationCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
// focus moved to Request Code
regCodePage.rememberRegistrationCode.sendKeys(protractor.Key.TAB);
expect(regCodePage.requestCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
// focus moved to Cancel
regCodePage.requestCode.sendKeys(protractor.Key.TAB);
expect(regCodePage.cancelButton.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
// focus moved back to the input
regCodePage.cancelButton.sendKeys(protractor.Key.TAB);
expect(regCodePage.registrationCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
});
where regCodePage is a Page Object:
var RegCodePage = function () {
this.title = element(by.css("div.modal-header b.login-modal-title"));
this.registrationCode = element(by.id("regCode"));
this.rememberRegistrationCode = element(by.id("rememberRegCode"));
this.requestCode = element(by.id("forgotCode"));
this.errorMessage = element(by.css("div.auth-reg-code-block div#message"));
this.sendRegCode = element(by.id("sendRegCode"));
this.cancelButton = element(by.id("cancelButton"));
this.closeButton = element(by.css("div.modal-header button.close"));
};
module.exports = RegCodePage;
It is working, but it is not really explicit and readable which makes it difficult to maintain. Also, another "smell" in the current approach is a code duplication.
If the current approach is how you would also do it, I would appreciate any insights about making it reusable.
I think the PageObject should define a tab order list, since that is really a direct property of the page, and should be expressible as simple data. An array of items seems like a sufficient representation, so something like:
this.tabOrder = [ this.registrationCode, this.rememberRegistrationCode, this.requestCode, this.cancelButton ];
Then you need a bit of generic code that can check a tab order.
function testTabOrder(tabOrder) {
// Assumes TAB order hasn't been messed with and page is on default element
tabOrder.forEach(function(el) {
expect(el.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
el.sendKeys(protractor.Key.TAB);
});
}
Then your test would be something like:
it('has correct tab order', function() {
var regCodePage = new RegCodePage(); // this should probably be in the beforeEach
testTabOrder(regCodePage.tabOrder);
});
Of course, this assumes each element has a "getId()" method that works. (That seems like a reasonable assumption to me, but some environments may not support it.)
I think this keeps the tab-order nicely isolated on the PageObject (so its easy to keep in sync with the page content and doesn't get lost in the code that verifies the order). The testing code seem "optimistic" (I suspect the real world will introduce enough problems that you will end up expanding this code a bit).
I haven't tried any of this yet, so feel free to downvote if this doesn't work. :)
Also, I believe the forEach loop will work as-is, but I wouldn't be surprised if it needs some more explicit promise handling to make the dependencies explicit.

How to Change Highlighted Items Color in Alfresco Share Form

so I have been trying to change the highlight color in which documents show up in the "items" field within an Alfresco Share workflow form. Basically, given a starting form that looks like this...
You will notice that every other document that gets added to the items field is automatically highlighted light blue. I was wondering if it was possible to change that color, and furthermore if it was possible to set it so only the top item (or a single items) gets highlighted in that list of documents?
I thought this would be as simple as finding and changing a CSS file somewhere, but despite changing a number of different CSS files within Alfresco, I have had little luck changing that color. Just wondering if anyone had any experience with this and would be willing to help me out?
EDITED:
The class you're looking for is:
tr.yui-dt-highlighted
The problem here is that these classes are generated automatically by JavaScript injected in the page. So, I've searched and found this little info:
path share/res/js/yui-common.js you should use a tool like JavaScript Formatting to understand some code in there. There is a CLASS_HIGHLIGHTED that starts a function, and you should try to override this:
highlightRow: function (k) {
var i = this.getTrEl(k);
if (i) {
var j = this.getRecord(i);
c.addClass(i, d.CLASS_HIGHLIGHTED);
this.fireEvent("rowHighlightEvent", {
record: j,
el: i
});
return;
}
},
unhighlightRow: function (k) {
var i = this.getTrEl(k);
if (i) {
var j = this.getRecord(i);
c.removeClass(i, d.CLASS_HIGHLIGHTED);
this.fireEvent("rowUnhighlightEvent", {
record: j,
el: i
});
return;
}
},
There are highlightRow and highlightColumn to look into. It's always very difficult to override YAHOO yui functions..

Categories

Resources