I'm trying to use Deface to add a button in a column in a table in Spree Admin. But I just can't get my CSS-like selector right. I can select the table row using a data-hook, and can select child elements (eg td, span), but can't select by a specific class (in this case, .balance_due). Am I missing something simple??
My override:
Deface::Override.new(:virtual_path => "spree/admin/orders/index",
:name => "add_capture_order_shortcut2",
:insert_bottom => "[data-hook='admin_orders_index_rows'] .balance_due",
:text => '<h1>hey yo, your balance is due</h1>'
)
I have confirmed the CSS selector using jQuery, ie:
$("[data-hook='admin_orders_index_rows'] .balance_due")
=> [<span class="state balance_due">…</span>]
An exerpt from the generated HTML:
<tr data-hook="admin_orders_index_rows" class="state-complete odd">
...
<td class="align-center"><span class="state balance_due">balance due</span></td>
...
</tr>
Deface acts upon the actual .html.erb template, before it is rendered, not the generated output.
You need to ensure that the selector matches something that is directly in your template specified in your virtual path.
The relevant line from the spree template is:
<td class="align-center"><span class="state <%= order.state.downcase %>"><%= Spree.t("order_state.#{order.state.downcase}") %></span></td>
Note that .balance_due is not specified directly in the template, but assigned from a variable.
The easiest way to fix this would be to override the entire
[data-hook='admin_orders_index_rows']
with your content, or use some fancy CSS selectors like :first-child to get the exact portion you want.
A better way to fix this would be to submit a pull request to spree with data-hook's to allow people to select specific rows with deface. That will be much less likely to break in the future if someone decides to re-order things, or add a new row.
Related
I want to add a button for each row as shown in the picture on the button place which shall give a popup of a field content of the same modal or whatever i insert for it to show.
This is actually, quite easy to do :) Although depending upon what you want the button to do, it might get a bit more complicated. You can have anything you want in your list view, just create a method that returns whatever you want to insert, and list it in list_display. For example:
class MyModelAdmin(admin.ModelAdmin):
list_display = (
"post_title",
...
"my_button_field",
)
def my_button_field(self, obj):
# obj is the particular instance of the object representing a particular row
return obj.property
In the case of a button, you'll want to use format_html. This will mark the relevant parts of the string safe, and so that it's displayed as an HTML button, and escape the other parts to protect against various securit issues.
It will look something like this:
from django.utils.html import format_html
class MyModelAdmin(admin.ModelAdmin):
list_display = (
"post_title",
...
"my_button_field",
)
def my_button_field(self, obj):
return format_html(
"<button onclick=`doSomething({})`>{}</button>",
obj.id,
obj.some_property
)
The values of object.id and object.some_property will get inserted into the placeholders.
Of course you'll need to add the javascript for doSomething, there are afew ways to do that, but you can find out more here. If what you want can be achieved by an anchor, it might be easier to use an <a> tag, and that way you won't have to bother with any additional javascrpt.
I need to generate a Page Object Model for a page to use with Selenium WebDriver. My page is so complex that the 'Selenium Page Object Generator' plugin generates 5000 lines of code from the code body without any filter.
My Requirement: I need to filter and retrieve only the page objects from the code block under HTML id = "xyz" (example).
My Question: What should be the filter criteria under 'Root Selector' in the above options window? Or is there any other options that I must try.
Note: I am raising this question due to the lack of information/guidelines available to use this plugin. Any informative links are also appreciated.
Try adding (body) in the root selector text-box
After trying the plugin for a little bit I don't think the root selector accepts operators like (=)
What you can do is to narrow your criteria a little bit
Example :
You need to get all the web elements under a certain table
You can add (table,tbody,td) in the root selector and the tool will get all the Elements under all the tables in your page having below hierarchy :
<table>
<tbody>
<td>
Element1
Element2
Element3
</td>
</tbody>
</table>
I get JSON like this
{
"lots of":"keys"
"description" : {
"key":"Some sample key",
"value":"This is the markup™"
}
}
from server and I ultimately iterate the description objects and populate table rows with two columns: one for the key and one for the value.
I have tried putting on my <td> tag ng-bind-html as well as injecting $sce into my controller and using trustAsHtml but so far the string always displays as it is in the JSON. Not every value will be HTML but I can easily detect based on the key if HTML is a possibility. It seemed when I put in the directive on the td it did not display anything if no HTML was present. I am interested in using something that can allow HTML in the value but not require it so I can display either
HTML fragment
<tr ng-repeat="(key, val) in record.description">
<td>{{key}}:</td>
<td>{{val}}</td>
</tr>
I created a quick fiddle here:
https://jsfiddle.net/frishi/mvw97s3q/6/
I used angular-sanitize, which I am not sure you mentioned injecting in your module dependency list. Either way, the example works simply by using ng-bind-html
Relevant docs page: https://docs.angularjs.org/api/ng/directive/ngBindHtml
It works by using the directive ng-bind-html on the element you want to display the HTML string in. You use it like so:
<p ng-bind-html="data.firstName"></p>
assuming that data.firstName = "<strong>FeeFee</strong>" or something like that.
I would also like to add that Angular does not allow this natively because of legitimate security concerns. That and the fact that allowing arbitrary HTML to be rendered might not always produce desirable results. Your page layout could quite possibly break because of some HTML you allowed to be passed through.
Angular was designed with security in mind, and will prevent you from displaying HTML from raw strings whenever possible - to prevent various injection attacks.
Here is workarround for your problem: AngularJS: Insert HTML from a string. Generally you should use ng-bind-html insted of ng-bind (this is used by curly braces).
I have a problem using the footable plugin with angular,
the problem is that footable appends new table rows to my table, but it pastes raw html text instead of angular replaced values, I mean {{'COMMENT'|translate}} instead of 'Comment' (see 'appended table image')
appended table image
I added click event, to perform special action to reevaluate, but I am not sure how to rebind the element HTML.
Table html
<th data-hide="phone,tablet" >{{'QUANTITY'| translate}}</th>
...
<tr ng-repeat="item in items | filter:itemsFilter" on-last-repeat ng-click="rowExpanded($event)" >
<td>{{item.Stock}}</td>
...
</tr>
Please let me now if you need more info. I'm quite stuck, tried googling, but i thing I don't know the term of my problem.
EDIT: It seems to me that footable caches table headers on init, and then reuses those values that are not compiled by angular yet, maybe that could help to find the answer.
Problem is that angular doesn't know that details row exist, and bindings inside it aren't replaced with values
expanded first row with console view
I figured out, that I need to recompile next element that is appended to the footable, I added ng-click on table rows:
<tr ng-repeat="item in items | filter:itemsFilter" on-last-repeat ng-click="rowExpanded($event)" >
Then in the controller, I found that currentTarget.nextElementSibling will return, next table row, which happens to be that uncompiled, new details row, so $compile does the trick. However this doesn't work when the row is already expanded and window being is resized, added columns in the details row will be in {{variablename}} syntax. But I guess I can live with that downside
$scope.rowExpanded = function($event){
$timeout(function(){
console.log($event.currentTarget.nextElementSibling);
$compile($event.currentTarget.nextElementSibling)($scope);
});
};
EDIT: Ok this didn't work when I used the <progressbar> template. Because at the time when footable reads data it is just a bunch of divs, and when i ask angular to compile those, it cant compile because bunch of divs isn't a >progressbar<
But yet, I found another solution, I delayed the footable initialization after everything is done on angular side:
TL;DR; I removed class 'footable' from table so it wont initialize automatically and call .footable() on the table element only after all of the ng-repeat rows were added (and compiled) to the table. This solves all my current footable/angular relation problems.
Try spacing out your translation statement, so instead of
{{'COMMENT'|translate}}
try
{{ 'COMMENT' | translate }}
So, I have made some custom directive which draws kind of a data-grid, based on floated divs (because nested flex implementation in FF sucks - but it's not the point).
How it works :
I pass some data collection to the directive via something like <the-grid data-list="parentController.displayedRows">
Inside this first directive, I have columns via something like <a-grid-column data-value="row.value"></a-grid-column> with many attributes I won't precise here.
The data-value value can be a direct expression, bound to the row on which the the-grid directive controller is ng-repeating in order to display each columns, or a function which have to be $eval-uated in order to display the intended value from the parentController.
In my <the-grid> directive controller, I have the rendering template of my grid which make a nested ng-repeat div (first one on the rows of the data-collection, second one on the columns, created in the directive), it looks like :
<div data-ng-repeat="row in list">
<div data-ng-repeat="cell in theGridColumns"
data-ng-bind-html="renderCell(row, cell)">
</div>
</div>
I have some keyboard nav in order to quickly select a row or navigate within pagination or many tabs, which does nothing more than applying some class on the selected row, in addition to update some "selectedRowIndex".
I'm using angular-vs-repeat in order to have the minimum of row divs in my DOM (because the app is running on slow computers). This works well.
The problem is that every time I'm hitting some "up" or "down" key on my keyboard, Angular is "redrawing" EVERY cells of the list.
So, let's suppose I've 200 rows in my data list, and 7 columns for each rows. First load of the page, it passes ~3000 times in the renderCell() function. Ok , let's accept that (don't really understand why, but ok).
I hit the down key in order to go to the second line of my list. It passes ~1100 times in the renderCell() function.
So yes, the result is very slow (imagine if I let the down arrow key pressed in order to quick navigate within my rows)... I can't accept that. If someone could explain that to me... Any help would be greatly accepted :)
If I make the same thing without a directive (direct DOM manipulation, with columns made by hand and not in a ng-repeat within a ng-repeat), every thing is smooth and clean.
Yes, I've look into every angular grid on the market. No one is satisfying me for my purpose, that's why I've decided to create my own one.
And no, I really can't give you some JSFiddle or anything for the moment. The whole app is really tentacular, isolating this is some kind of complicated).
Try to use bind once (angular 1.3+)
<div data-ng-repeat="row in ::list">
<div data-ng-repeat="cell in ::theGridColumns"
data-ng-bind-html="::(renderCell(row, cell))">
</div>
</div>