I'm using Angular Material 2 Tree component. In the provided example, when you add a new item, the edit box is displayed at the bottom, how can I add it to at the first position, so we can always see the added edit box as in my use case I can add many new items.
See https://stackblitz.com/angular/gjjyykvkrdll?file=app%2Ftree-checklist-example.ts
In your ChecklistDatabase class, change insertItem() method from a .push to a .splice
/** Add an item to to-do list */
insertItem(parent: TodoItemNode, name: string) {
if (parent.children) {
//parent.children.push({item: name} as TodoItemNode);
parent.children.splice(0,0,{item: name} as TodoItemNode);
this.dataChange.next(this.data);
}
}
Stackblitz
https://stackblitz.com/edit/angular-ifr2tc?embed=1&file=app/tree-checklist-example.ts
In my code, i use unshift()
insertItem(parent: FileNode, name: string) {
const child = <FileNode>{ name: name,'parent_id': parent.parent_id};
if (parent.children) {
parent.children.unshift(child);
this.dataChange.next(this.data);
} else {
parent.children = [];
parent.children.unshift(child);
this.dataChange.next(this.data);
}
Related
LazyColumn has item keys, in order to tie the item's state to a unique identifier rather than the list index. Is there a way to use item keys in a non-lazy list like this one?
Column {
for (item in list) {
Text(item)
}
}
The reason I ask is because I want to implement SwipeToDismiss to delete items from a list, which only works if you pass a key to a LazyColumn (solution), but my list of dismissable items is nested inside of a LazyColumn, and I can't nest a LazyColumn inside of another LazyColumn's itemContent block (Nesting scrollable in the same direction layouts is not allowed):
val items = listOf<String>(...)
val groups = items.groupBy { it.first() }
LazyColumn {
items(groups, { /* key */ }) { (firstChar, group) ->
// not allowed!
LazyColumn {
items(group, { /* key */ }) { item ->
Text(item)
}
}
}
}
I could wrap the items() call itself in a for loop like this:
val items = listOf<String>(...)
val groups = items.groupBy { it.first() }
LazyColumn {
groups.forEach { (firstChar, group) ->
items(group, { /* key */ }) { item ->
Text(item)
}
}
}
But then state in each of the outer loop's items would be keyed against its index. And I need to provide item keys for groups as well, in order to preserve their state on position changes.
The general pattern for this is,
for (item in items) {
key(item) {
... // use item
}
}
The key composable is special and Compose will use item as a key to detect when the state should move when an the value of item moves in the items collection.
I am trying to show a list of Orders in a list using LazyColumn. Here is the code:
#Composable
private fun MyOrders(
orders: List<Order>?,
onClick: (String, OrderStatus) -> Unit
) {
orders?.let {
LazyColumn {
items(
items = it,
key = { it.id }
) {
OrderDetails(it, onClick)
}
}
}
}
#Composable
private fun OrderDetails(
order: Order,
onClick: (String, OrderStatus) -> Unit
) {
println("Composing Order Item")
// Item Code Here
}
Here is the way, I call the composable:
orderVm.fetchOrders()
val state by orderVm.state.collectAsState(OrderState.Empty)
if (state.orders.isNotEmpty()) {
MyOrders(state.orders) {
// Handle status change click listener
}
}
I fetch all my orders and show in the LazyColumn. However, when a single order is updated, the entire LazyColumn gets rrecomposed. Here is my ViewModel looks like:
class OrderViewModel(
fetchrderUseCase: FetechOrdersUseCase,
updateStatusUseCase: UpdateorderUseCase
) {
val state = MutableStateFlow(OrderState.Empty)
fun fetchOrders() {
fetchrderUseCase().collect {
state.value = state.value.copy(orders = it.data)
}
}
fun updateStatus(newStatus: OrderStatus) {
updateStatusUseCase(newStatus).collect {
val oldOrders = status.value.orders
status.value = status.value.copy(orders = finalizeOrders(oldOrders))
}
}
}
NOTE: The finalizeOrders() does some list manipulation based on orderId to update one order with the updated one.
This is how my state looks like:
data class OrderState(
val orders: List<Order> = listOf(),
val isLoading: Boolean = false,
val error: String = ""
) {
companion object {
val Empty = FetchOrdersState()
}
}
If I have 10 orders in my DB and I update one's status (let's say 5th item), then OrderDetails gets called for 20 times. Not sure why. Caan I optimize it to make sure only the 5th indexed item will be recomposed and the OrderDetals gets called only with the new order.
Is the Orderclasss stable? If not it could be the reason why all the items get recomposed:
Compose skips the recomposition of a composable if all the inputs are stable and haven't changed. The comparison uses the equals method
This section in the compose's doc explains what are stable types and how to skip recomposition.
Note: If you scroll a lazy list, all invisible items will be destroyed. That means if you scroll back they will be recreated not recomposed (you can't skip recreation even if the input is stable).
I know there is a GridFieldExportButton which exports all the data of a GridField. But what I want is a custom Button which exports all the $db fields (ore just a few of them) of only ONE DataObject in a CSV file and download it. So I want this button in the edit area of this one DataObject and not for the GridField which shows all data objects.
I have already the button, now I need the right function. Can somebody help me?
You can change the fields exported for any DataObject in ModelAdmin with the following:
ModelAdmin:
class MyModelAdmin extends ModelAdmin {
...
static $managed_models = array(
'MyDataObject'
);
...
public function getExportFields() {
$modelClass = singleton($this->modelClass);
return $modelClass->hasMethod('getExportFields')
? $modelClass->getExportFields()
: $modelClass->summaryFields();
}
...
}
MyDataObject:
class MyDataObject extends DataObject {
...
public function getExportFields() {
$exportFields = array(
//Add all "db" fields here
);
return $exportFields;
}
...
}
If you wish for the export to be attached to a button else where I would suggest that you change the button to link to the ModelAdmin export CSV link
I would like to provide a possibility to show my components in a bit different look and feel and thought using the decorator for it. Something like:
<body>
<my-component my-decorator></my-component>
</body>
.
#Component(
selector: 'my-component',
templateUrl: '.../my-component.html',
cssUrl: '.../my-component.css',
publishAs: 'comp',
)
class MyComponent {
MyComponent(final Element element) {
Logger.root.fine("MyComponent(): element = $element, element.attributes = ${element.attributes.keys}");
}
}
#Decorator(selector: '[my-decorator]')
class MyDecorator {
final Element element;
#NgOneWay('my-decorator')
var model; // is not used
MyDecorator(this.element) {
Logger.root.fine("MyDecorator(): element = $element, element.nodeName = ${element.nodeName}");
Logger.root.fine("MyDecorator(): element.shadowRoot = ${element.shadowRoot}, element.parent = ${element.parent}");
}
}
Unfortunately, it seems that my-decorator is processed before my-component so it is getting null shadowRoot property in the injected Element object.
It would be possible to check on existence of the my-decorator attribute within the my-component backing class, but that is clearly polluting the design.
UPDATE: Thanks to replay from Marko Vuksanovic, the following is now returning the :
#Decorator(selector: '[my-decorator]')
class MyDecorator extends AttachAware {
final Element element;
#NgOneWay('my-decorator')
var model; // is not used
MyDecorator(this.element) {
Logger.root.fine("MyDecorator(): element = $element, element.nodeName = ${element.nodeName}");
Logger.root.fine("MyDecorator(): element.shadowRoot = ${element.shadowRoot}, element.parent = ${element.parent}");
}
void attach() {
Logger.root.fine("attach(): element.shadowRoot = ${element.shadowRoot}");
}
}
The question still remains how to modify the styling of the shadow DOM.
Thanks in advance for any comments/ideas/solutions.
You can try using AttachAware and it's attach method. You should implement AttachAware interface in your decorator and/or component.
Here's link to Angular.dart docs - https://docs.angulardart.org/#angular-core-annotation.AttachAware
To change the styling of a ShadowDom component you can use element.shadowRoot to get the root of your web component. Shadow root is almost like 'document' object. You can use shadow root to get reference to any element and then you can easily modify it by applying styles as needed.
You could use something like
this.element.shadowRoot.querySelector('[some-attr]').innerHtml = "Modified by decorator" // disclaimer: not tested, but I hope you get the idea.
You can add a style tag to the shadowDom programmatically:
shadowRoot.append(new StyleElement()..text = ':host{background: red;}');
or
shadowRoot.append(new StyleElement()..text = "#import url('some.css')");
I am using the shape tracer in order to use an alternate view of one of my taxonomie fields called location. However unlike other shapes the alternates do not give the option for different display types in this case summary or details. So by changing 1 you change the other. I need to be able to do them independently.
I have a view created see below Fields.Contrib.TaxonomyField-Location.cshtml but as i say this is rendered the same if the display type is details or summary.
How to i overcome this please.
Thanks Jon
I had the same problem not long ago. You can provide your own alternates by implementing a ShapeDisplayEvents class.
Here is an implementation that gives you alternates based on the content type, display type, or both:
public class PartContentTypeAlternateFactory : ShapeDisplayEvents {
public override void Displaying(ShapeDisplayingContext context) {
context.ShapeMetadata.OnDisplaying(displayedContext => {
var shapeType = displayedContext.ShapeMetadata.Type;
var contentItem = displayedContext.Shape.ContentItem;
var displayType = displayedContext.ShapeMetadata.DisplayType;
var contentType = contentItem.ContentType;
displayedContext.ShapeMetadata.Alternates.Add(
String.Format("{0}__{1}", shapeType, displayType));
displayedContext.ShapeMetadata.Alternates.Add(
String.Format("{0}__{1}__{2}", shapeType, (string)contentType, displayType));
});
}
}
You will end up with some extra alternates appearing in the shape tracer, like this:
And you can now use an alternate named Fields.Contrib.TaxonomyField-Location-Summary or Fields.Contrib.TaxonomyField-Location-Detail etc. You can extend this class to add whatever alternates you want.
Edit
I didn't realise you couldn't easily get to the field name, so try something like this - it does something similar to what the UrlAlternatesFactory does, i.e. it loops over the existing alternates and adds the displayType to them. This should give you an alternate that contains both the field name and the display type.
public class PartContentTypeAlternateFactory : ShapeDisplayEvents {
public override void Displaying(ShapeDisplayingContext context) {
context.ShapeMetadata.OnDisplaying(displayedContext => {
var alternates = displayedContext.ShapeMetadata.Alternates.Select(a => a + "__" + displayedContext.ShapeMetadata.DisplayType);
displayedContext.ShapeMetadata.Alternates = displayedContext.ShapeMetadata.Alternates.Union(alternates).ToList();
});
}
}