Dynamic search/filter core-list (Polymer 0.5) - polymer

I need to implement a filter-type search which hides items in core list if they do not match the search. I created a .hidden class that is applied to an item if an expression returns false:
class = {{ {hidden: !match(model.host, hQuery)} | tokenList }}
The elements are hidden, but the list does not reflow elements unless I click on a visible row. Is there a way to force a reflow using a function call?

After a week of struggling, hiding list items is just not the right way to handle this. Iterate through the original array, push any matching objects to a temporary array, and then replace core-list's .data array with the temporary array: this.$.list_id.data = tmpArray. Performance is good for lists up to 10K records.

This is what I'm doing in my code, and it works:
<div style="{{hide_part1}}">
...content to show/hide...
</div>
....
Switching it based on route change(flatron-director):
routeChanged: function(oldValue, newValue) {
if ('some_route_1' == this.route) {
this.hide_part1 = ''
this.hide_part2 = 'display: none;'
} else if ('some_route_2' == this.route) {
this.hide_part1 = 'display: none;'
this.hide_part2 = ''
}
},
Also using core-list's updateSize() and especially scrollToItem(0), i.e. back to top, here and there helps as I also had problems with the 'reflow':
https://stackoverflow.com/questions/29432700/polymer-core-list-is-not-rendering-some-of-the-elements

Related

Add "+5" if there is no space for more elements

I have a div (ItemsContainer) that has an array of items (Item) being rendered inside of it. The div has a dynamic size, depending on the size of the screen.
As I'm mapping through the array, I'd like to be able to make a check to see if there is enough space to render the current item. If there isn't, I'd like to stop rendering the items and instead add another item that says "+(number of items not rendered in array)". See the included picture for reference.
So far, this is what my code looks like. I'm using React typescript. I haven't attempted adding the "+5" box yet, because I'm wondering if it's actually possible? My initial thought is to just have a fixed number of items be displayed and then display the + item if there are more items not rendered, but I was hoping you could do it a bit more dynamic.
const Items: FC<Props> = ({ items }) => {
return (
<ItemsContainer>
{items.map((item, index) => (
<Item key={index}>{item.name}</Item>
))}
</ItemsContainer>
);
};
Here is a partially working solution: https://codepen.io/Latcarf/pen/WNKZmBN.
The main part of the code is the following:
const Tags = ({tags}) => {
const [hiddenCount, setHiddenCount] = React.useState(0)
const shownTags = hiddenCount == 0 ? tags : tags.slice(0, -hiddenCount);
const ref = React.useRef();
React.useLayoutEffect(() => {
if (!ref.current) {
return;
}
if (ref.current.scrollWidth > ref.current.clientWidth) {
setHiddenCount(count => count + 1);
}
});
return (
<div class="container" ref={ref}>
{shownTags.map(tag => <div class="tag">{tag}</div>)}
{hiddenCount > 0 && <div class="tag">{`+${hiddenCount}`}</div>}
</div>
)
};
The idea is to keep a hiddenCount state variable that contains the number of hidden tags. It starts at 0, and then a layout effect checks whether there is space and keeps incrementing it until there is enough space. It's a bit inefficient if you have many hidden items, as it will rerender many times until it reaches the correct amount, but it should properly deal with edge cases like if adding +1 would actually be longer than displaying the last tag, or stuff like that.
In order to make it update automatically on resize, you would use some kind of useResizeObserver hook to reset hiddenCount to 0 after a resize. Somehow I couldn’t manage to import other packages in CodePen, so I just made a button that forces a rerender so that you can test it (resize the div and then click the button).

VueJs - Updating class with a setInterval function not working [duplicate]

I'm new to Vuejs. Made something, but I don't know it's the simple / right way.
what I want
I want some dates in an array and update them on a event. First I tried Vue.set, but it dind't work out. Now after changing my array item:
this.items[index] = val;
this.items.push();
I push() nothing to the array and it will update.. But sometimes the last item will be hidden, somehow... I think this solution is a bit hacky, how can I make it stable?
Simple code is here:
new Vue({
el: '#app',
data: {
f: 'DD-MM-YYYY',
items: [
"10-03-2017",
"12-03-2017"
]
},
methods: {
cha: function(index, item, what, count) {
console.log(item + " index > " + index);
val = moment(this.items[index], this.f).add(count, what).format(this.f);
this.items[index] = val;
this.items.push();
console.log("arr length: " + this.items.length);
}
}
})
ul {
list-style-type: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.11/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
<div id="app">
<ul>
<li v-for="(index, item) in items">
<br><br>
<button v-on:click="cha(index, item, 'day', -1)">
- day</button>
{{ item }}
<button v-on:click="cha(index, item, 'day', 1)">
+ day</button>
<br><br>
</li>
</ul>
</div>
EDIT 2
For all object changes that need reactivity use Vue.set(object, prop, value)
For array mutations, you can look at the currently supported list here
EDIT 1
For vuex you will want to do Vue.set(state.object, key, value)
Original
So just for others who come to this question. It appears at some point in Vue 2.* they removed this.items.$set(index, val) in favor of this.$set(this.items, index, val).
Splice is still available and here is a link to array mutation methods available in vue link.
VueJS can't pickup your changes to the state if you manipulate arrays like this.
As explained in Common Beginner Gotchas, you should use array methods like push, splice or whatever and never modify the indexes like this a[2] = 2 nor the .length property of an array.
new Vue({
el: '#app',
data: {
f: 'DD-MM-YYYY',
items: [
"10-03-2017",
"12-03-2017"
]
},
methods: {
cha: function(index, item, what, count) {
console.log(item + " index > " + index);
val = moment(this.items[index], this.f).add(count, what).format(this.f);
this.items.$set(index, val)
console.log("arr length: " + this.items.length);
}
}
})
ul {
list-style-type: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.11/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
<div id="app">
<ul>
<li v-for="(index, item) in items">
<br><br>
<button v-on:click="cha(index, item, 'day', -1)">
- day</button> {{ item }}
<button v-on:click="cha(index, item, 'day', 1)">
+ day</button>
<br><br>
</li>
</ul>
</div>
As stated before - VueJS simply can't track those operations(array elements assignment).
All operations that are tracked by VueJS with array are here.
But I'll copy them once again:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
During development, you face a problem - how to live with that :).
push(), pop(), shift(), unshift(), sort() and reverse() are pretty plain and help you in some cases but the main focus lies within the splice(), which allows you effectively modify the array that would be tracked by VueJs.
So I can share some of the approaches, that are used the most working with arrays.
You need to replace Item in Array:
// note - findIndex might be replaced with some(), filter(), forEach()
// or any other function/approach if you need
// additional browser support, or you might use a polyfill
const index = this.values.findIndex(item => {
return (replacementItem.id === item.id)
})
this.values.splice(index, 1, replacementItem)
Note: if you just need to modify an item field - you can do it just by:
this.values[index].itemField = newItemFieldValue
And this would be tracked by VueJS as the item(Object) fields would be tracked.
You need to empty the array:
this.values.splice(0, this.values.length)
Actually you can do much more with this function splice() - w3schools link
You can add multiple records, delete multiple records, etc.
Vue.set() and Vue.delete()
Vue.set() and Vue.delete() might be used for adding field to your UI version of data. For example, you need some additional calculated data or flags within your objects. You can do this for your objects, or list of objects(in the loop):
Vue.set(plan, 'editEnabled', true) //(or this.$set)
And send edited data back to the back-end in the same format doing this before the Axios call:
Vue.delete(plan, 'editEnabled') //(or this.$delete)
One alternative - and more lightweight approach to your problem - might be, just editing the array temporarily and then assigning the whole array back to your variable. Because as Vue does not watch individual items it will watch the whole variable being updated.
So you this should work as well:
var tempArray[];
tempArray = this.items;
tempArray[targetPosition] = value;
this.items = tempArray;
This then should also update your DOM.
Observe object and array reactivity here:
https://v2.vuejs.org/v2/guide/reactivity.html

React: How to provide procedurally generated <li> elements distinct HTML id values?

I'm rendering a map of items retrieved from a database and filtered via the value state of an input field and attempting to then set the state of the input field as the value stored in some list item on click. I figured that using document.getElementById().innerHTML would allow me to retrieve the content stored within the appropriate tag and then set it to state which does work, the issue I'm facing is that it will only retrieve the innerHTML of the first item rendered in the map.
I've tried solutions ranging from applying UUID to making the mapped content available to the window and transfering the state of the individual objects but each disparate solution only moves the value of the first item to state - any ideas?
Rendered Content:
window.filteredItems = this.state.items.filter(
(item) => {
return item.companyNameObj.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1;
}
);
<div className="fixed-width">
<div className="search-container">
<form>
<input type="text" name="search" className="search-bar" placeholder="Search: " onChange={this.handleChange} value={this.state.search} />
</form>
<ul className="search-results">
{window.filteredItems.map((item) => {
return (
<div className="distinct-result-container">
<li key={item.id}>
<div className="image-container">
<img src={item.imageObj} alt={item.companyNameObj + " logo."}/>
</div>
<div className="company-container">
<span onClick={this.stateTransfer}><h3 id={"ID"}>{item.companyNameObj}</h3></span>
<p>Owned by: {item.ownerNameObj}</p>
</div>
</li>
</div>
)
})}
</ul>
</div>
<Footer />
</div>
);
stateTransfer()
stateTransfer(id) {
var search = this.state.search;
var uniqueID = document.getElementById("ID").innerHTML;
this.setState({
search: uniqueID
});
}
The current content of stateTransfer() doesn't represent any significant attempts at approaching a solution to this issue, it's just the minimum required implementation to move the innerHTML content to the input fields value.
EDIT: I've further clarified on the task at hand and a potential solution in the comments below (which follow this), I'm just hoping someone is able to help me with the actual implementation.
#DILEEPTHOMAS The list is comprised of data pulled from a Firebase Realtime Database and is rendered via mapping the filteredList and a search query; that functoionality works fine - what I need is to be able to click the element of any distinct li and have the innerHTML (the text stored in that li's item.companyNameObj) be moved to the value of the input field (so users can navigate the search content with re-typing).
#JoshuaLink I can't necessarily configure the items of the list any
further as it's just data pulled from an external database - I believe
the appropriate solution is to somehow provide a unique HTML ID value
to each newly rendered li and have that selected ID moved to
stateTransfer() where it can be set as the input fields value, I'm
just struggling with the actual implementation of this.
EDIT 2: I've managed to figure out a solution to both parts of the problem as described above - I'll post it as an answer below.
I managed to solve both parts of my problem:
The key issue, which was moving the text stored in each distinct li to the input value, which was apparently easily solved by making my stateTransfer() function accept an event and passing the .innerText value of the h3 through the event (I assumed I would have to use .innerHTML, which would require me to provide each distinct li with a unique generated ID) as follows:
stateTransfer(e) {
var search = this.state.search;
var innerText = e.target.innerText
this.setState({
search: innerText
})
}
The secondary issue, (which I incorrectly assumed was integral to implementing a solution to my question), assigning unique HTML id values to my procedurally generated li's was solved by implementing a for-loop in a componentDidUpdate() function which iterates through the current total length of the list and and assigns an id with the loop iterator concatenated to the end of the string as follows:
componentDidUpdate() {
var i;
var searchCompanyNames = document.querySelectorAll('.comapnyNames');
for(i = 0; i < searchCompanyNames.length; i++) {
searchCompanyNames[i].id = 'companyName-' + i;
}
}
Whilst I didn't need to assign unique ID's to the li's in the correct implementation, it's a useful trick worth noting nonetheless.

PrimeNG p-orderList disable multiple selection of items

I am using PrimeNG's p-orderList. By default, the metaKeySelection attribute is true which implies that a metaKey(ctrl key) is needed to be pressed to select multiple items. I was rather looking for a way to completely disable selection of multiple items. I should be able to select ONLY ONE item in the ordered list.
There is no metaKey attribute available for p-orderList. Can anyone help me with this?
<p-orderList [value]="policyList" [listStyle]="{'min-height':'calc(100vh - 325px)'}" (onSelectionChange)="onSelectionChange($event)">
<ng-template let-policy pTemplate="policy">
<span>{{policy}}</span>
</ng-template>
</p-orderList>
PS: onSelectionChange($event) is triggered every time you select items from the ordered list. $event.value contains the array of the items.
There is no easy flag for it but it can be achieved through calling a function that basically replaces the entire selection array with just the original selected row.
You will need a variable to store the previous value for comparison.
onSelectionChange(event) {
if (event.value.length === 1) {
this.tempValue = event.value[0];
}
else {
event.value = [this.tempValue];
}
}
Can also be simplified by passing event.value to the function
(onSelectionChange)="onSelectionChange($event.value)">
What about the metaKeySelection input property? (as shown here)
<p-orderList [metaKeySelection]="false" [value]="policyList" [listStyle]="{'min-height':'calc(100vh - 325px)'}" (onSelectionChange)="onSelectionChange($event)">
<ng-template let-policy pTemplate="policy">
<span>{{policy}}</span>
</ng-template>
</p-orderList>

How do i populate an Array in one class based on a textfield in another class?(Actionscript 3.0)

i have a class (TheList.as). in which i have an array "Data" and it has a couple of values. Then i have a loop through which i am creating a scrollable list which uses the values from "Data" array. [I am trying make a unit converter]
Then i have another class "Units.as". In that class i have created three instances of "TheList". A main list ("myList"), and to sublists "ListFrom" and "ListTo". They are using values from "Data" array. Now i have text field whose value changes to whatever item is clicked. When i click "Angle" in the main list, i want the sublists to get populated with ("Degree", "Radian" etc)..
Here is what i tried
if(myList._TextLabel.text == "Angle")
{
ListFrom.Data = ["Degree", "Radian"];
}
But nothing happens, i do not get any error either. When i do this in an "ENTER_FRAME" event and trace (ListFrom.Data), i can see that the values change, but they do not get assigned to the list items in the list. I would really appreciate the help. Thanks!
Here are complete Classes for understanding the situation better(the code is pretty messy, as i am a newbie to OOP)
TheList.as: http://pastebin.com/FLy5QV9i
Units.as : http://pastebin.com/z2CcHZzC
where you call ListFrom.Data = ["Degree","Radian"], make sure when the data changed, the renders in the ListFrom have been set new data. for example, you may use MyRender in ListFrom for show, you should debug in the set data method in MyRender.
you should call the code below after you call ListFrom.Data = ["Degree","Radian"];
for (var i:int = 0; i < Data.legnth;i++) {
var render:MyRender = ListFrom[i] as MyRender;
if (render) {
render.data = Data[i];
} else {
var render:MyRender = new MyRender();
render.data = Data[i];
ListFrom.addChild(render);
}
}
You can use event listeners, singleton classes or reference one class to another, depending on the style you want. All are equally valid and fast / efficient.