Access directive element from a controller - javascript

On my page I have a table with a custom directive that shows row item's detailed view when a user click on details button :
<table ng-controller="MyController">
<tr ng-repeat="item in controller.items">
<td>{{item.name}}</td>
<td> details
</td>
</tr>
<tr ng-details details-colspan="2" ng-model="controller.details"></tr>
</table>
Directive:
.directive('ngDetails', function() {
return {
restrict: 'A',
require: "^ngModel",
replace: true,
template: '<tr class="detailsTemplate {{ngModel.cssClass}}"> \
<td colspan="{{detailsColspan}}" ng-bind-html="ngModel.data"></td> \
</tr>',
scope: {
detailsColspan: '#',
ngModel: '='
}
}
});
Here's a demo.
Right now item details section is showing fine, but it's always at the bottom of the table. I would like to move my directive row element under the corresponding item row, but I'm not sure how to access the directive element inside of my controller.

Rather than having the details row appear only once and be moved around, let the ng-repeat include it for every row. You can do this using ng-repeat-start on the first <tr> and ng-repeat-end on the last <tr>. This special syntax allows arbitrary sets of elements to be repeated without being contained in the same repeating element. Then you can include a ng-hide, ng-show, or possibly ng-if on the directive element to only be displayed when the appropriate row has been clicked.
More information about the ng-repeat-start/end syntax can be found here.

You shouldn't access your element inside your controller. If you have to do that, than there's something wrong with either your design of the application or your understanding of Angular itself.
At a general level, you should start thinking of your directives as the glue between HTML and business logic (your actual JS code inside your services, libraries, models, etc...) and be very strict of keeping it this way.
In this particular case, you could think of the whole ng-repeat as part of one directive, and incorporate the detail row between each item rows.
Even better, if showing the detail row requires some action from the user, like a click on the item row, you can dynamically append/remove the detail row just under the clicked item row. This will be a little bit more optimal since the detail data will be "lazy" compiled.

Related

Stimulus: how to handle repeating items with the same target name

I have a list of items, and each one has a link to click to edit it. I am using stimulus to make the edit "modal" form visible when they click that edit link. The id of what is going to be edited is present as an id= on the corresponding link tag of the list
So, the edit link looks like this:
<td>
<a data-action="click->content#edit"
data-target="content.editBtn"
id="<%= url_for(content) %>")>
Edit
</a>
</td>
And the idea is that the content#edit action in the stimulus controller examines the and locates the id of it and uses that to edit the right row.
However the problem I am having is, I think, that as a result all the rows of this list have a data-target with the same name and the wrong one (the first one?) gets bound to the target..
However if I wanted to make each data-target different by e.g. appending the id to it, now I have a long list of targets in the controller.js so that doesn't make sense.
What's the right way to handle?
If you're using Rails as the backend like your other questions seem to indicate, there may be a simpler, non-Stimulus solution. To use Stimulus, you'd need to fetch the data for the item from the server or from the DOM, display it in a form, then submit the correct form with the correct ID to the server through JavaScript. Why not just have a remote link_to button to the edit action for each item? Rails gets a JS request to the edit controller action, and you can load the modal form with the data that you have from your Ruby object.
If you use Stimulus for anything on the form, I'd use this opportunity to craft a Stimulus controller that listens to the ajax->send/error/complete events and automatically disables/enables buttons, sets loading spinners on buttons, and closes the modal. Those would be good areas to sprinkle in some functionality that Stimulus makes very simple.
This is actually a good use of Stimulus since it is modular. You add a controller for each row instead of having the controller around the page or table.
<tr data-controller="content">
<td>
<a data-action="click->content#edit" data-target="content.editBtn" id="<%= url_for(content) %>")>
Edit
</a>
</td>
</tr>
I was just having a similar problem.
This helped:
https://codepen.io/smashingmag/pen/ExPprPG
Basically you can loop over the targets like:
for(let tgt of this.mySameNameTargets) {
tgt.innerHTML = "Some Value"
}
This assuming you have this in the controller:
state targets = ["mySameName"]
You can also use "Action Parameters" to put the id in each row:
https://stimulus.hotwired.dev/reference/actions#action-parameters
From those docs it looks like this:
<div data-controller="item spinner">
<button data-action="item#upvote spinner#start"
data-item-id-param="12345"
data-item-url-param="/votes"
data-item-payload-param='{"value":"1234567"}'
data-item-active-param="true">…</button>
</div>
// ItemController
upvote(event) {
// { id: 12345, url: "/votes", active: true, payload: { value: 1234567 } }
console.log(event.params)
}
You can set the id of the item in that row in a param, and then when they click on that row you can dig it out of the params.

Get table attribute or property value within ember controller

I have an ember application which has a table and a grid component within a page. I have enabled drag and drop feature which drags a value from a particular cell of a table and drops it into the empty space of the grid. As the tables have more than one column, I want to get the index position or I want to know which column's cell is being dragged. I want that index value of column within the controller.
Suppose a table has 3 rows with 3 columns. For all the elements within the first column being dragged, I want to get the index value as 1 similarly for 2nd and 3rd column.
Here is my .hbs code for the table
<table class = "table table-bordered">
<thead>
<tr>
<th colspan="6">Inbound Units</th>
</tr>
</thead>
<tbody>
{{#each currentbid as |currentbid|}}
<tr>
{{#each pull as |pull|}}
{{#if (eq pull.DRIVERNAME currentbid.DRIVERNAME)}}
<td abbr="P1">{{#draggable-object content=pull position=1 dragEndAction='dragEndAction'}}{{#draggable-object-target action="draganddrop"}}{{pull.P1}}{{/draggable-object-target}}{{/draggable-object}}</td>
<td>{{pull.P2}}</td>
<td>{{pull.P3}}</td>
<td>{{pull.P4}}</td>
{{/if}}
{{/each}}
</tr>
{{/each}}
</tbody>
</table>
As you can see wihtin the table td tag, I have specified abbr attribute. But I have no idea how to get the value of abbr within the controller. Any other way of getting this is also fine.
Thanks !
This answer applies to Ember 2.x.x and was written as of 2.15.
By default, actions assigned to a specific event via closure, like ondragEnd={{action "someAction"}} receive the event target as an argument:
actions: {
someAction(event) {
console.log(event.target)
}
}
Possibly, you could use event.target.parentElement.className in your component to get the class name, then send the action and argument to your controller. Hopefully that selector will return the new parent and not the old one.
You can read about different ways to catch browser events in the Ember Guides.

How to use ng-repeat inside a directive proper way in angularjs

Plunkr
I made a autofill dropdown using angular custom directive.All is working fine.
But I want to make this directive reusable!!!!
Currently my directive is tightly depended on Id and Name property of selected item.
I want to assign text property(Which I want to bind the text into list items html) and value property(which will be available in my app controller) like textfield="Name" and value-field="Id".
Imagine If my data object does not contain Name or Id property , my
directive would not work.
HTML:
<autofill-dropdown items="searchCurrencies"
textfield="Name" valuefield="Id" selected-data="Id" urllink="/api/SetupCurrency/Autofill?filter=">
</autofill-dropdown>
Template :
<div class="pos_Rel">
<input placeholder="Search Currency e.g. doller,pound,rupee"
type="text" ng-change="SearchTextChange()" ng-model="searchtext"
class="form-control width_full" />
<ul class="currencySearchList" ng-show="IsFocused && items.length">
<li class="autofill" ng-repeat="item in items" ng-click="autoFill(item)">
{{item.Name}}
</li>
</ul>
</div>
So,finally what I want to accomplish , I'm going to bind Name property(or other,depends on what value I set to "text-field" attribute onto my html) to listitems and whenever user select an item, it will give me the selected Id(or other property ,depends on what value I set to "value-field" attribute onto my html).
Plunkr Here
Update:
I think my question not so clear.
I have written same directive 2 times to accomplish and also 2 times same template :(
Please look through my updated plunkr.
Updated Plunkr
I want to say that I am not quit sure if I understood your question in the right way so I try to repeat it.
textfield="Name" valuefield="Id" selected-data="Id"
You want to store the Value of the Property of the selected Item of your custom directive.
The Value should be stored in the Variable provided in the attribute "textfield"
The attribute "valuefield" should define the name of the property of the selected Object
If this is the case you have to get rid of the two-way databinding for your valuefield. Because you only want to provide a String (like "Id" or "Name").
I altered your plunkr again. First of all I made the new scope variables:
scope: {
'items': '=',
'urllink':'#',
'valuefield':'#',
'textfield':'=',
'Id':'=selectedData'
}
Valuefield is now only a text binding. So if you want to change it in the angular way you have to provide a scope variable outside of the directive and a '=' twoway binding inside :D
$scope.autoFill = function (item) {
$scope.IsFocused = false;
$scope.searchtext = item.Name;
$scope.Id = item.Id;
$scope.textfield = item[$scope.valuefield];
}
Also I added the last expression to your autofill function. The name of the property (valuefield) will be written into the textfield variable which is linked with your outside scope (in this case "Name"):
textfield="Name"
If this is not what you asked I am sorry but I am new to stackoverflow and I still can not comment :P
https://plnkr.co/edit/zE1Co4nmIF3ef6d6RSaW?p=preview

alternating class in ng-repeat using scope var without binding

I have a nested ng-repeat on a table. It is an accordion table with child rows for each parent row. In order to accomplish this I created a <tbody> for each parent item, placing the parent row in a <tr> and then using ng-repeat to add all the child rows. Due to the multiple <tbody> elements the zebra striping on the table gets thrown off. Another wrinkle is that the table has the ability to collapse/expand child rows and I need the striping to be correct for whichever rows are visible. So I am trying to manually add striping classes. I'm using Angular's ng-init to toggle a scope variable, and then using ng-class to apply it. The problem is that it appears to be bound to the final state of the variable rather than what it was as the iterator was rendering the row.
HTML:
<tbody ng-repeat="parentRow in myData" ng-init="changeRowClass($even)">
<tr ng-class="{'industry-payments-even-row':industryRowEven,'industry-payments-odd-row':!industryRowEven}">
<td>{{parentRow.industryCode}} - {{parentRow.industryCodeDescription}}</td>
<td class="numeric">{{parentRow.ValueOneSum}}</td>
<td class="numeric">{{parentRow.ValueTwoSum}}</td>
</tr>
<tr ng-repeat="childRow in parentRow.childIndustries" ng-init="changeRowClass($even)" ng-class="{'industry-payments-even-row':industryRowEven,'industry-payments-odd-row':!industryRowEven}">
<td>{{childRow.industryCode}} - {{childRow.industryCodeDescription}}</td>
<td class="numeric">{{childRow.ValueOne}}</td>
<td class="numeric">{{childRow.ValueTwo}}</td>
</tr>
</tbody>
Controller code:
$scope.industryRowEven = false;
$scope.changeRowClass = function(isEven) {
$scope.industryRowEven = isEven;
};
I need each iteration (of parent OR child) to reverse the class for the next one (I'm leaving out the issue of if the child is visible or not for now to keep this more simple). I can see in the debugger that the variable is getting toggled properly on each iteration. The problem is that the ng-class seems to be bound to the current state in scope so when it is true it would apply one class, but then next time it is false and switches the class for all of them.
I need it to just print the class according to the variable state at the time it renders that row, and then ignore the variable unless the ng-repeat is started over (like for sorting or toggling visibility, etc.)
Is there a way to stop the binding?
AngularJS has this build in with directives called ngClassOdd and ngClassEven.
They work just like ngClass, but only on odd/even items inside an ngRepeat.

Emberjs show table cell after click on link

I have a table that is built from items in a DB and they contain sensitive information that I don't want to display until you click on a link contained in another table cell.
|item1|item2|click to show item3|(hidden span within cell)|
When you click on the link in cell 3 it will then show cell 4. I know how to accomplish this in typical jquery, but am not sure how to accomplish this in emberjs. Any ideas?
A JS fiddle of your setup would make this easier, but basically you would set a property on your controller from an action.
<span {{action showCell4}}>click to show item3</span>
on your controller have the showCell4 action:
actions: {
showCell4: function() {
this.set('cell4visible', true);
}
}
then for the table add a class binding
<td {{bind-attr class="cell4visible:visibleClassName:hiddenClassName">
sensitive info here
</td>

Categories

Resources