AngularJS toggle getting blocked by Math function - javascript

I have rows where cells are populated with words from an API. I've added a toggle feature on the cells to show if a user selected it. I'm using ng-class={selected:toggle} ng-click="toggle = !toggle" to toggle a CSS class to show the cell is selected or not.
HTML
<tr ng-repeat="row in game.rows">
<td id="row-{{$parent.$index+1}}-col-{{$index+1}}" ng-repeat="word in row.words" ng-class={selected:toggle} ng-click="toggle = !toggle"><div class="points">{{generateRandomPoints()}}</div>{{word}}</td>
</tr>
I added a Math function to the controller to randomly assign points to each cell (<div class="points">{{generateRandomPoints()}}</div>):
JavaScript
$scope.generateRandomPoints = function(){
return Math.floor((Math.random()*5)+2);
};
This function appears to be blocking my ability to use the toggle feature. Another oddity is that each click of a cell re-generates random numbers from the Math function.
Any thoughts why the toggling would be blocked?

It appears your generateRandomPoints call is punching through the local scope of your repeated element. Try creating a function like this:
$scope.toggle(index) {
game.rows[index].toggle = !game.rows[index].toggle;
}
And call it like ng-click="toggle($index)".
Full documentation for ngRepeat can be found here.

Related

(Updated) Trying to edit the behaviour of toggle function in tablesorter with a nested table

(This post has been updated for clarity and more of my findings.)
I'm currently using the tablesorter functions toggle and tablesorter-childRow to make a table that has a child row that can be hidden and expanded by clicking 'toggle' on the primary row like this example. However, the child row contains a nested table where the first row also contains a toggle function that can hide or expand all the remaining rows of that nested table. Here is my table in action.
Currently, the toggle functions all work fine in the sense that they are able to hide and expand the rows that I want to. However, if I click on the primary row's toggle function (the row with Category I), the function expands the entire nested table. I would like the toggle function on the primary role to only expand the first row of the nested table only (the row with Category II).
For my script, I used the default script found on the cssChildRow configuration here. What I tried to do is to give the rows that I want expanded its own html class (in the fiddle they are marked with <td class="show">). Then I edited the to script function to .find('.show') instead of the default .find('td'), but this did not work. Does anyone have a solution?
Thanks for everyone's help! 😊
<script>
$(function() {
$('.tablesorter-childRow td').hide();
$(".tablesorter")
.tablesorter({
cssChildRow: "tablesorter-childRow"
})
$('.tablesorter').delegate('.toggle', 'click', function() {
$(this).closest('tr').nextUntil('tr:not(.tablesorter-childRow)').find('.show').toggle();
return false;
});
});
</script>

Expand Row of Table on Click and draw D3 charts inside

I am trying to create a Table in which a row expands on click and two D3 charts are drawn inside the new expanded row.
My logic for Expand Row:-
<tbody>
<tr ng-repeat-start="item in nodeSummary">
<td>
<button ng-if="item.expanded" ng-click="tableRowExpand($index,false)">-</button>
<button ng-if="!item.expanded" ng-click="tableRowExpand($index,true)">+</button>
</td>
<td>{{item.a}}</td>
<td>{{item.b}}</td>
<td>{{item.c}}</td>
</tr>
<tr ng-if="item.expanded" ng-repeat-end>
<td colspan="4" style="height:100px;width:100%;">
<div id="expanded-tablerow-container" style="height:100%;width:100%;overflow:auto;" >
<div id="expanded-tablerow-circleProgress" style="height:100%;width:20%;float:left;">Here</div>
<div id="expanded-tablerow-barchart" style="height:100%;width:80%;float:left;">Here</div>
</div>
</td>
</tr>
</tbody>
So using ng-repeat-start/end, and onClick of button the particular row is expanded/shrinked.
tableRowExpand function:-
$scope.tableRowExpand = function(index, value){
$scope.nodeSummary[index].expanded = value;
if (value){
drawCircularProgressBar(d3.select("#expanded-tablerow-circleProgress"),0.8);
drawHorizontalBar("#expanded-tablerow-barchart",data);
}
};
So on checking value is true I am calling methods to draw d3 chart and bar. But they are not drawing.
My Problems:-
Well, I looked into the problem and it seems the problem is calling these method before the changes to the document is done, How I know this? Well, suppose if there are two rows, then on expanding 1st row, nothing is drawn but on then clicking the 2nd row, bar and chart is drawn on 1st row's expanded space. Am I able to make myself clear? So how to fix this problem?
Multiple rows can remain expanded at a time. I may have 100s of rows, and the d3 drawing is done based on data of each row which will vary, so each row will have unique d3 drawing on its expanded view. But as the drawing is done by passing the divs, which for all expanded rows will be same, won't this kill the uniqueness, saying same drawing on all expanded view?. In a way, how can I have unique drawing for each row?
Hope I was able to make myself clear. Please help me resolve this.
Thanks.
Well I was finally able to solve it. Lets go for it.
Problem 1)
Yeah I was right :) The problem is that the document was not fullt loaded, I solved adding a simple check code,
angular.element(document).ready(function(){
//calling the d3 draw code here
});
Problem 2)
I don't know if this is the most efficient way, if someone want to input, please do, but I did this.
var newCircleProgressId = 'expanded-tablerow-circleProgress' + index;
var newHoizontalBarId = 'expanded-tablerow-barchart' + index;
document.getElementById("expanded-tablerow-circleProgress").setAttribute('id',newCircleProgressId);
document.getElementById("expanded-tablerow-barchart").setAttribute('id', newHoizontalBarId);
i.e. dynamically altering the ID of the div inside the tableRowExpand function and passing this new ID along with row unique(extracted using index) to draw d3 functions.

How to track the progress of a large OrderBy

I have a standard enough dynamically filled table in bootstrap that is instantiated with the following definition
<table>
<tr ng-repeat="c in controller.items :
orderBy:controller.predicate:controller.reverse">...</tr>
</table>
And a pretty standard function that, when called with a string argument, will sort the table, using the string argument as the predicate.
controller.orderCasesBy = function (predicate) {
controller.reverse = (controller.predicate === predicate) ?
!controller.reverse : false;
controller.predicate = predicate;
}
The page I'm working on potentially has upwards to a thousand rows, so I was trying to figure out a way to track the progress of the ordering function and update a progress bar accordingly.
If this were a case where I was trying to track the progress of the entire table rendering for the first time, I could attach an ng-init function to each repeated row that could update the progress bar whenever it's called. ng-init functions don't seem to be called when a table is reordered, however, so I was wondering if there was anything I could do to achieve a similar effect.
If I can't, I'd love to hear suggestions on alternative ways to track this.
You can add an object parameter to the ordering function, which updates its value property (or call it whatever you want), then use the object.value to render your status indicator as you see fit.
Since you said you're open to alternatives, you don't need to actively reverse it at all (and therefore don't need to indicate progress for it). ngRepeat allows you to track by index, which means you can simply display them in reverse index order.
Example:
<table>
<tr ng-repeat="c in controller.items track by $index">
<td ng-model="controller.items[controller.items.length-($index+1)]"></td>
</tr>
</table>

Overriding difficult view model

I am trying to replace some text in an input field using JS but the view model overrides my commands each time. This is the HTML I start with:
<td class="new-variants-table__cell" define="{ editVariantPrice: new Shopify.EditVariantPrice(this) }" context="editVariantPrice" style="height: auto;">
<input type="hidden" name="product[variants][][price]" id="product_variants__price" value="25.00" bind="price" data-dirty-trigger="true">
<input class="mock-edit-on-hover tr js-no-dirty js-variant-price variant-table-input--numeric" bind-event-focus="onFocus(this)" bind-event-blur="onBlur(this)" bind-event-input="onInput(this)">
</td>
I run this JS:
jQuery('#product_variants__price').siblings().removeAttr('bind-event-focus');
jQuery('#product_variants__price').siblings().removeAttr('bind-event-input');
jQuery('#product_variants__price').siblings().removeAttr('bind-event-blur');
jQuery('#product_variants__price').siblings().focus()
jQuery('#product_variants__price').siblings().val("34.00");
jQuery('#product_variants__price').val("34.00");
And I'm left with the following HTML:
<td class="new-variants-table__cell" define="{ editVariantPrice: new Shopify.EditVariantPrice(this) }" context="editVariantPrice" style="height: auto;">
<input type="hidden" name="product[variants][][price]" id="product_variants__price" value="34.00" bind="price" data-dirty-trigger="true">
<input class="mock-edit-on-hover tr js-no-dirty js-variant-price variant-table-input--numeric">
</td>
The problem is that each time I click the input field the value is reverted to what it was when the page loaded.
I've also tried running the command in the parent td along with my value change, to simulate the editing of a variant and preventing default with no success:
jQuery('#product_variants__price').siblings().bind('input', function (e) {
e.preventDefault();
return false;
});
jQuery('#product_variants__price').siblings().bind('focus', function (e) {
e.preventDefault();
return false;
});
jQuery('#product_variants__price').siblings().focus()
jQuery('#product_variants__price').siblings().val("£34.00");
jQuery('#product_variants__price').val("£34.00");
jQuery('#product_variants__price').siblings().keydown()
Parent td function:
new Shopify.EditVariantPrice(jQuery('#product_variants__price').parent())
So how can I successfully edit this value in the inputs and also update the Shopify view model?
You can try this for yourself by going here:
https://jebus333.myshopify.com/admin/products/2521183043
login jebus333#mailinator.com
password shop1
EDIT: I've tried to find the view model on the page but with no success. Plus, there are no network calls when editing the values in the input fields, leading me to believe the values are being pulled back from somewhere on page.
Try this:
var old = Shopify.EditVariantPrice.prototype.onFocus;
Shopify.EditVariantPrice.prototype.onFocus = function(t) {
this.price = '50.00'; // Use the price you want here
old.call(this, t);
};
jQuery('#product_variants__price').siblings().triggerHandler("focus");
jQuery('#product_variants__price').siblings().triggerHandler("blur");
If it works for you, it's possible that the following will be sufficient:
Shopify.EditVariantPrice.prototype.onFocus = function(t) {
this.price = '50.00'; // Use the price you want here
};
Well, there is a kind of a dirty solution...
First of all you'll need a sendkeys plugin. In fact that means you'll need to include this and this JS libraries (you can just copy-paste them in the console to test). If you don't want to use the first library (I personally find it quite big for such a small thing) you can extract only the key things out of it and use only them.
The next step is creating the function which is going to act like a real user:
function input(field, desiredValue) {
// get the currency symbol while value is still pristine
var currency = field.val()[0];
// move focus to the input
field.click().focus();
// remove all symbols from the input. I took 10, but of course you can use value.length instead
for (var i = 0; i < 10; i++) field.sendkeys("{backspace}");
// send the currency key
field.sendkeys(currency);
// send the desired value symbol-by-symbol
for (var i = 0; i < desiredValue.length; i++) field.sendkeys(desiredValue[i]);
}
Then you can simply call it with the value you wish to assign:
input($("#product_variants__price").next(), "123.00");
I did not really manage to fake the blur event because of lack of the time; that is why I was forced to read the currency and pass .00 as a string. Anyway you already have a way to go and a quite working solution.
Looks like you're trying to automate editing of variant prices of products in Shopify's admin panel.
Instead of playing around with the DOM of Shopify's admin page, I'll suggest using Shopify's bulk product editor which lets you set prices of all variants in a single screen. I feel that you'll have better luck setting the variant prices using JavaScript on the bulk product editor page.
Clicking on the 'Edit Products' button as shown in the screenshot below will open the bulk product editor.
Also check if browser based macro recording plugins like iMacro can be of your help (you can also code macros with JS in iMacro).

How can I modify an array model in an Angular end-to-end test?

I've got an angular app which has a table defined in the controllers like so:
$scope.rowHeaders = ["Revenues","Costs","Profit"];
$scope.data = [[100,230,300,400,500,600,700,800,900,1000,1100,1200], [30,40,50,60,70,80,90,100,110,120,130,140], [70,160,250,340,430,540,630,720,810,900,990,1100]];
The rowHeaders apply to each row in the data object. This is connected to the template as follows:
<tbody>
<tr ng-repeat="row in rowHeaders track by $index">
<td>{{row}}</td>
<td ng-repeat="cellData in data[$index] track by $index">
<input id="data{{$parent.$index}}_{{$index}}" type="text" class="clsinputboxes" ng-model="data[$parent.$index][$index]" name="data{{$parent.$index}}_{{$index}}" ng-blur="updateColor($parent.$index, $index, data[$parent.$index][$index])" style="background-color: {{color[$parent.$index][$index]}}">
</td>
</tr>
</tbody>
This produces a simple table with input boxes. When the input in a box changes, the background color of the box will also change with code that is inside updateColor(), specified in the controller.
All of this works fine. I want to be able to write a dead-simple end-to-end test, that will change the contents of one of the boxes in the table, and then check that the updateColor() function was triggered properly (perhaps by checking that the background color has indeed changed). This is proving to be incredibly hard to do.
I had thought something as simple as the following should have worked:
input("data[1][1]").enter(20);
But this actually does not work, and I get the following error:
http://localhost:8000/test/e2e/scenarios.js:17:4
Selector [ng\:model="data[1][1]"] did not match any elements.
Whereas, the code works fine when not in test mode, and the model is bound properly in the input tag using ng-model="data[$parent.$index][$index]".
For and end-to-end test, how do I go about entering some data into any box in the table that is linked up to an array model?
I also tried the following (although I'd much rather work with input().enter()):
element("#data1_1").query(function(el, done) {
el.click();
el.val(20);
done();
});
This changes the box contents but does not trigger updateColor(). I also tried putting e1.blur() - that does not work either.
I was able to finally get this to work in the elegant end-to-end scenario runner of Angular.
First, I gave an id to the table tag - "inputTable".
Then, the following in the scenario runner did the trick:
using("table#inputTable tbody tr:eq(1) td:eq(2)").input("data[$parent.$index][$index]").enter(20);
A shoutout to Ari Lerner who helped me figure this out.

Categories