django tables2 display sort arrows on header (follow-on question) - django-tables2

Building off the question and answer in this prior question, what django-tables2 puts out comes out as: example table where heading text is the link and the arrows are not active. When I create the table, my template uses {% render_table table %}
The example in the twitter blog has a better UX because the entire table header cell is the link. How do I do that with django-tables2?
Thanks
Following up with some experimentation, I've been able to make table <td> cells work label via bootstrap, django-tables2 meta attrs, and render_col(). However, I cannot get the <th> to work. The th definition sets the container fine, but I cannot get the subtending <a> that django-tables2 generates to include reference to the bootstrap class. I can't find a render() for table headers.
Here's my code:
attrs = {"class": "table table-striped table-sm ",
"th": {"class": "col-md-2 position-relative",
"a": {"class": "stretched-link"},
},
"td": {"class": "col-md-2 position-relative"},
}
and here's what gets generated:
<th class="asc col-md-2 orderable position-relative" a="{'class': 'stretched-link'}">
First name
</th>

Solved it this way after some experimentation and search. Turns out the key is a bootstrap utility (stretch-link) and putting this class on the link rather than the th elements. Open to better solutions.
class StudentTable(tables.Table):
class Meta:
model = Student
fields = ("first_name", "last_name", "nickname", "status")
sequence = ("last_name", "first_name", "nickname", 'status')
attrs = {"class": "table table-striped table-sm "}
def make_string(self, value, record):
temp = reverse('client:student_information', kwargs={'pk': record.id})
the_str = f"<a href='{temp}' class='url-no-underline stretched-link'> {value} </a>"
return the_str
def render_first_name(self, value, record):
return mark_safe(self.make_string(value, record))

Related

React table v8 - how to render custom cell content?

I'm using #tanstack/react-table v8, and I want to have some icons and buttons in one of my table row cells. Looking at the docs I can use a display column for this:
Display columns do not have a data model which means they cannot be sorted, filtered, etc, but they can be used to display arbitrary content in the table, eg. a row actions button, checkbox, expander, etc.
The docs also mention this about custom rendering of cell content:
By default, columns cells will display their data model value as a string. You can override this behavior by providing custom rendering implementations. ... You can provide a custom cell formatter by passing a function to the cell property ...
The docs show this example for custom rendering of a cell:
columnHelper.accessor('firstName', {
cell: props => <span>{props.getValue().toUpperCase()}</span>,
})
So, I have defined a display column using ColumnHelper:
columnHelper.display({
id: "actions",
cell: (props) => (
<div className="w-10 h-10 bg-red">
<span>actions?</span>
</div>
),
}),
How do I access this render function when creating the table? I'm creating the table body as follows (the table variable is returned by the useReactTable hook):
<tbody>
{table.getRowModel().rows.map((row, index) => (
<tr className={classNames(index % 2 !== 0 && "bg-tableRowGray")}>
{row.getAllCells().map((cell) => (
<td>{cell.getValue()}</td>
))}
</tr>
))}
</tbody>
cell.getValue() and cell.renderValue() do not call the custom render function and I don't see any other render functions on the cell object. Am I just overlooking something really simple here?
Turns out I was overlooking something simple. #tanstack/react-table exports a flexRender function which is used to render the cell, e.g.
import {flexRender} from "#tanstack/react-table";
...
<tbody>
{table.getRowModel().rows.map((row, index) => (
<tr className={classNames(index % 2 !== 0 && "bg-tableRowGray")}>
{row.getAllCells().map((cell) => (<td>{flexRender(cell.column.columnDef.cell, cell.getContext())}</td>))}
</tr>
))}
</tbody>

Add dynamic styling to received data Angular Material

I want to add some css styling to highlight each text like:
I do not want any cross button and I don't want to give user any option to add tags. I am receiving data from RESTful api in Angular 8 as text separated by , (comma) and I am displaying data as text. It looks like a paragraph. I want to replace each text sentence like this by splitting it on , (comma).
TS code:
this.user.data = response.Items;
for (let i = 0; i <= this.user.data.length; i++){
this.splitString[i] = this.user.data[i].abc.split(',');
this.finalSplittedString = [...this.splitString[i]];
}
I am receiving an array of object and I am storing it in user.data Now I am splitting the string on object parameter abc.
JSON OBJECT
[{"names":"Hello","abc":"abcdef,ghijk,lmnop"},{"names":"World","abc":"qrstuv,wxyz"}]
I want to split the ABC and write them as shown in image below. Also, they should be displayed in correct table row.
HTML Code
<ng-container matColumnDef="abc">
<th mat-header-cell *matHeaderCellDef> ABC </th>
<td mat-cell *matCellDef="let element" class="addTag"> {{element.abc}}</td>
</ng-container>
How can I bind the new splitted sting here?
The key point is to use split(",") function to convert a string into array, so that you can display it using *ngFor in html
I have created an example for you:
https://stackblitz.com/edit/angular-8-getting-started-xpndxn?file=src/app/product-list/product-list.component.ts
For ex, in your TS file you have this-
export class ChipsOverviewExample {
availableColors:string[];
sentence:string;
constructor(){
this.sentence="Green,red,pink";
this.availableColors=this.sentence.split(',');
}
}
In your HTML you can use basic chips-
<mat-chip-list aria-label="Fish selection">
<mat-chip class="chips" *ngFor="let chip of availableColors" >
{{chip}}
</mat-chip>
</mat-chip-list>
And in your CSS, you can style accordingly-
.chips{
background-color: chocolate;
}

target text after br tag using cheerio

I'm practicing creating an API by scraping using cheerio. I'm scraping from this fairly convoluted site:
http://www.vegasinsider.com/nfl/odds/las-vegas/
I'm trying to target the text after these <br> tags within the anchor tag in this <td> element:
<td class="viCellBg1 cellTextNorm cellBorderL1 center_text nowrap"
width="56">
<a class="cellTextNorm" href="/nfl/odds/las-vegas/line-movement/packers-#-
bears.cfm/date/9-05-19/time/2020#BT" target="_blank">
<br>46u-10<br>-3½ -10
</a>
</td>
The code below is what i'm using to target the data I want. The problem I'm having is I don't know how to get that text after the <br> tags. I've tried .find('br') and couldn't get it to work. Here is the code:
app.get("/nfl", function(req, res) {
var results = [];
axios.get("http://www.vegasinsider.com/nfl/odds/las-vegas/").then(function(response) {
var $ = cheerio.load(response.data);
$('span.cellTextHot').each(function(i,element) {
// console.log($(element).text());
var newObj = {
time:$(element).text()
}
$(element).parent().children().each(function(i,thing){
if(i===2){
newObj.awayTeam = $(thing).text();
}
else if (i===4){
newObj.homeTeam = $(thing).text();
}
});
newObj.odds= $(element).parent().next().next().text().trim();
$('.frodds-data-tbl').find('td').next().next().children().each(function(o, oddsThing){
if(o===0){
newObj.oddsThing = $(oddsThing).html();
}
});
res.json(results);
});
});
You can see I am able to output all the text in this box to the newObj.odds value. I was trying to use something like the next line where I'm targeting that td element and loop through and break out each row into its own newObj property, newObj.oddsLine1 and newObj.oddsLine2 for example.
Hope that makes sense. Any help is greatly appreciated.
You can't select text nodes with cheerio, you need to use js dom properties / functions:
$('td a br')[0].nextSibling.nodeValue
Note $(css)[0] will give you the first element as a js object (rather than a cheerio object)

AngularJS directive to render a JSON tree as indented table

I want to render a multi-column tree (i.e. tree table or tree grid) in Angular. My data is a tree of arbitrary depth, and looks something like this:
[
{name: "node 1",
type: "folder",
children: {[
{name:"node 1-1", type:file},
{name:"node 1-2", type:file},
{name:"node 1-3", type:file},
]}
},
...
...
]
I've seen examples that use recursive templates to render this in a ul/li tree, but I need multiple columns, so I think I need a table. So far the best HTML I have come up with to render the above data structure would look something like:
<tr>
<td>
node 1
</td>
<td>
folder
</td>
</tr>
<tr>
<td>
<span class="treetable-padding"/>
node 1-1
</td>
<td>
file
</td>
</tr>
...
...
So, in other words it's just a flat table where the tree depth is represented using a number of span elements, with that number being equal to that level in the hierarchy.
I am not great with html/AngularJS/js in general so I don't know if this is the best way to render my data, but it certainly looks the way I want.
My question is, what's the best way to do this using AngularJS? I know I can build a monolithic chunk of html like this in JavaScript and then wrap that in a custom directive, but I'm looking for a solution that's more in the spirit of Angular, so that I can take advantage of data binding etc. I ultimately want the cells to be editable so I don't want to do anything that will make data binding messy.
I thought of writing some code to flatten my tree into a list of objects that explicitly contain a "depth" attribute. Then maybe I could use ng-repeat. But I thought maybe there is a cleaner way using one or more custom directives
Thanks!
I have something that works now. It works in the way I described in my comment on my question above. Just to recap, I'm using 2 custom directives, both of which are based on ng-repeat. The first one builds a flat table structure with literal values in the class attribute to indicate depth. The second directive uses that depth information to repeat the padding element the appropriate number of times. Both repeaters use transclusion to minimize restrictions/dependencies on the html. The only caveat is that the second repeater needs the depth info to be somewhere in it's ancestry within the DOM.
Here's what the HTML template looks like for producing a tree-table of the structure I described in my question:
<table class="treetable" style="width: 40%;">
<tr class="treetable-node" my-repeat="item in things"
my-repeat-children="children">
<td>
<span my-repeat-padding class="treetable-node-indent"></span>
{{ item.name }}
</td>
<td>
{{ item.type }}
</td>
</tr>
</table>
I based my custom repeater on this example. What makes mine different is that you can specify a "children" property and the directive logic will check for that property on each item, and descend recursively into it if it's there - although it attaches all new elements to the same parent, giving me the flat structure.
It's probably pretty inefficient, and could use some optimization - like the code in the link I provided, it rebuilds everything when there's a change.
myApp.directive("myRepeat", function($parse) {
return {
restrict: "A",
replace: true,
transclude: "element",
link: function (scope, element, attrs, controller, transclude) {
match = attrs.myRepeat.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/);
itemName = match[1];
collectionName = match[2];
childCollectionName = attrs["myRepeatChildren"];
parentElement = element.parent();
elements = [];
scope.$watchCollection(collectionName, function(collection) {
var i, block, childScope;
if (elements.length > 0) {
for(i=0; i<elements.length; i++) {
elements[i].el.remove();
elements[i].scope.$destroy();
};
elements = [];
}
var buildHtml = function(parent, itemList, depth=0) {
for (var i=0; i<itemList.length; i++) {
childScope = scope.$new();
childScope[itemName] = itemList[i];
transclude(childScope, function(clone) {
parentElement.append(clone);
block = {};
block.el = clone;
block.scope = childScope;
block.el.addClass("depth-" + depth);
elements.push(block);
});
/*Recurse if this item has children,
adding the sub-elements to the same
parent so we end up with a flat list*/
if(childCollectionName in itemList[i]) {
buildHtml(parentElement,
itemList[i][childCollectionName],
depth+1);
}
}
}
for (i=0; i<collection.length; i++) {
buildHtml(parentElement, collection);
}
});
}
};
});
The second one is a bit hacky. It's also a transcluding repeater. It searches upwards in the DOM for the depth class attribute, parses out the depth value, and prepends itself that many times into the parent. So it's totally dependent on that depth class set by the first directive. There are probably better ways of doing it. I also didn't bother setting up a watch or anything like that since this is purely cosmetic.
myApp.directive("myRepeatPadding", function($parse) {
return {
restrict: "A",
replace: true,
transclude: "element",
terminal: true,
link: function (scope, element, attrs, controller, transclude) {
var getDepth = function(element) {
classes = element.attr("class");
if (classes) {
match = classes.match(/depth-([\d+])/);
if(match.length > 0) {
return match[1];
} else {
return getDepth(element.parent());
}
} else {
return getDepth(element.parent());
}
}
depth = getDepth(element);
for (var i=0; i<depth; i++) {
transclude(scope, function(clone) {
element.parent().prepend(clone);
block = {};
block.el = clone;
block.scope = scope;
elements.push(block);
});
}
}
};
});
It's far from perfect and I'll have to spend some time improving it. I'm not even sure if the HTML I'm producing is the best way to get the appearance I want - but it does render the way I want while keeping the html template looking reasonably elegant.

Webmatrix - WebGrid: change css style conditionally

I've just started with Webmatrix and I'm trying to transform an HTML table to the webgrid.
The data comes from the DB and I want to change the css class for each td (within one column) depending on the items content.
So for example if the item Contains "," then apply class="multi", if it doesn't contain "," and is not null then class="single", else "none".
<td class="multi">
<td class="single">
<td class="none">
I've tried it with the webgrid style: and format: settings but I couldn't get the classname to switch depending on the value of the item. I think I just need the right syntax to get started.
I hope you can help me out here.
Thank you.
If you want to use the WebGrid, your only real option is to set the td style based on the cell value via Javascript after it has rendered. The style parameter will only accept a string representing the CSS class to apply.
Alternatively, you can conditionally set the content in the format parameter based on the value, filling the td with a span or div that you can then style eg:
<style>
td.nopadding{padding: 0;margin: 0;}
.green{background-color:green;display: inline-block;width: 100%;height: 100%;}
.yellow{background-color:yellow;display: inline-block;width: 100%;height: 100%;}
</style>
<div id="grid">
#grid.GetHtml(
tableStyle : "table",
alternatingRowStyle : "alternate",
headerStyle : "header",
columns: grid.Columns(
grid.Column("Country", style: "nopadding", format: #<text>#Html.Raw(item.Country == "USA" ?
"<span class=\"green\">" + item.Country +"</span>" :
"<span class=\"yellow\">" + item.Country +"</span>")</text>))
)
</div>
UPDATE: The following code illustrates a more complex if..else statement being accommodated in the format parameter:
format: #<text>#if(item.YourProperty == null){
#Html.Raw("<span class=\"none\">" + some_value +"</span>")
}
else{
if(item.YourProperty.Contains(",")){
#Html.Raw("<span class=\"multi\">" + some_value +"</span>")
}
else{
#Html.Raw("<span class=\"single\">" + some_value +"</span>")
}
}
</text>

Resources