I'm building an SPA that can be described by the following image
Explanation:
- An object items exists in the controller of the component2.
- A user has the ability to manage this object by using the 4 different actions (buttons on the component1)
- These actions (for the sake of simplicity) can refer to a different kind of plot. i.e When Action 1 is pressed, data inside items object can be plotted as a pie chart, when action 2 is chosen will be plotted as a histogram and so on.
- These plots are going to appear in within the various ng-include templates.
In general, and so far, the way I approached it, is to use a service (injected into both of the components). When action buttons are clicked, an event is broadcasted from the service and when this event reaches the component 2, its controller sets the corresponding variable (action-1-on, action-2-on, action-3-on, etc) into true and the contents of the ng-include appear.
The problem I'm facing lies exactly in the last step. Let's say for example that inside of one of these templates (e.g action1.html) I have a range element and I want to initialize it as a noUiSlider element. To do that, on the $onInit function of the component2 controller, I add the following code:
var slider = document.getElementById('test-slider');
noUiSlider.create(slider, {
start: [20],
step: 1,
orientation: 'horizontal',
range: {
'min': 0,
'max': 100
},
format: wNumb({
decimals: 0
})
});
What I'm getting back is the error:
TypeError: Cannot read property 'nodeName' of null
and from that, I assume that javascript cannot find the element with the id #test-slider
So, what I want to ask before solving this error, is if this approach (with service and the two components) is reasonable and "angularish" or there is a more straight and clear way to do this (maybe ui-route?). I prefer to work with the more angular way than try to solve this error. If the way I'm approaching is ok, how am I gonna make that element initialized from the $onInit function? Should I add some kind of delay?
Any ideas and opinions are welcome.
Related
I was wondering if this issue has happened to anyone else whilst using tabulator.
I'm trying to create a table with the headerFilter set to select. i.e., inside the column Definitions:
{title: 'title', headerFilter:'select'...}.
The issue that I am having is that when setting {headerFilter:'select', headerFilterParams: {values: true}} this returns every possible filter option for the column. I'm aware this is probably the intended function of {values: true}, but I was wondering whether there is a way of only showing the option to filter on the data currently in the table (i.e., the filtered data).
I looked through the Tabulator documentation, and I cannot find anything related to the problem I'm having.
I did however create my own function to do this for me: which can be found here (Codepen.io). Uncommenting lines 30-32 will activate the function that I have created. There are issues with it. This codepen also showcases the two issues that are being mentioned. Aside being spaghetti code:
When using {frozen:true} in the column definitions, I'm getting the error: Uncaught TypeError: Cannot set properties of undefined (setting 'position') relating to FrozenColumns.js (line 295) element.style.position = "absolute";.
When adding values to headerFilterParams from the filtered data in the table. I suspect this is an issue with the object keys getting overwritten, instead of merging them together. But before I go down the route of trying to concatenate object with duplicates into one entry, I wanted to make this post.
If anyone could help me with any of these issues / inform me of a much easier and cleaner way to implement this function - it would be GREATLY appreciated. I've spent too many hours on trying to wrap my head around this issue.
Tabulator: v5.1.6.
jQuery: v.3.6.0
Thanks all in advanced :)
For those that have the same issue, I fixed the issue by first: upgrading to Tabulator v5.2, and then changing {headerFilter: "select", headerFilterParams:{values:true}} to {headerFilter: "list", headerFilterParams:{valuesLookup: "active"}}
Using Meteor and React, I am building an application that has a fabric canvas in the middle, a 'components panel' on the left (a library of objects to add to the canvas), and a 'layers panel' on the right (similar to Photoshop, where each object on the canvas is represented as a layer with specific controls like color, opacity etc.).
So far I have managed to display the Layers Panel by using a Meteor session variable that contains an array for the objects on the canvas.
So each time I add, remove, adjust or make any functional change to any object on the canvas, I manually make the adjustment for the object within the session variable array as well.
This has worked well so far but I now want to implement grouping functionality. While it is easy to do this with fabric.js alone, I am having trouble representing the exact state of the groups on the layers panel. It needs to be displayed in a tree format where nested groups and items are within/under other objects.
I also still need to have the layers panel sortable as well.
Using fabric, we can group a set of objects, group another set of objects, and then group the two sets of objects together. This gets quite complicated and is quickly becoming a nightmare to maintain using the method I described above.
So now I am thinking, it would be much easier to just have the layers panel look at the object data directly from the canvas (reactively). This way, I don't need to maintain a second array and manually match every change that occurs. I could then use something like React Sortable Tree.
So in an attempt to make this happen, I am trying to store a copy of canvas.getObjects() into a Meteor session variable after any change occurs on the canvas.
canvas.on({
'after:render': function() {
var canvasObjects = [];
var objects = canvas.getObjects();
objects.forEach(object=>{
canvasObjects.push(object);
});
Session.set('canvasObjects', canvasObjects);
}
});
I am getting the following error though...
TypeError: callback is not a function
at modules.js?hash=d9804d9fae07a146610fc3c3c610d3e79bf33006:261816
at onLoaded (modules.js?hash=d9804d9fae07a146610fc3c3c610d3e79bf33006:244313)
at modules.js?hash=d9804d9fae07a146610fc3c3c610d3e79bf33006:244337
at modules.js?hash=d9804d9fae07a146610fc3c3c610d3e79bf33006:257179
at onLoaded (modules.js?hash=d9804d9fae07a146610fc3c3c610d3e79bf33006:244357)
at modules.js?hash=d9804d9fae07a146610fc3c3c610d3e79bf33006:244379
at Array.forEach (<anonymous>)
at Object.enlivenPatterns (modules.js?hash=d9804d9fae07a146610fc3c3c610d3e79bf33006:244370)
at Function.fabric.Object._fromObject (modules.js?hash=d9804d9fae07a146610fc3c3c610d3e79bf33006:257171)
at Function.fabric.Path.fromObject (modules.js?hash=d9804d9fae07a146610fc3c3c610d3e79bf33006:261457)
If I replace this line...
Session.set('canvasObjects', canvasObjects);
with this line...
console.log(canvasObjects);
I see the array printed in the console.
Why is it able to log the array but unable to store it into the session variable?
Is this even a good idea? I feel like it will be bad for performance. Would there be any other way to keep the canvas state and the layers panel in sync with all the properties that I want (visibility, opacity, fill, lock, index, group hierarchy etc.).
Thanks in advance.
I'm encountering performance issues, I think due to lots of watchers in the page (more than 4000!!). The scenario is a (small, about 5) list of items in ng-repeat once, each one contains another ng-repeat for every day of week (so 7), and in each day container there are 1 or 2 input field. Each day's element has its own scope and controller and some watch at parent's properties, in order to update parent state at child changes. So a bit complex scenario...imagine an agenda view where each day as some input fields or buttons which update same property in the main scope, like "10 days selected/filled/clicked".
I started with about 5000 watchers, now reduced to about 4000 removing some filters and switching to translate-once directive insted of translate (angular-translate).
So the main question is:
How to further reduce the number of watchers?
Is every child scope inheriting the parent watchers, resulting in 7x for each watcher? If I remove child's controllers, leaving the job to the the parent (passing in the function the child item), will I decrease the number of watchers? Could this be a solution? Any help is appreciate.
In our experience that number of watchers cause no speed problems. The performance problems we have encountered in the last 8 months of development on a single big application were caused by slow third part's components.
For example, we have a page with two drag and drop trees with 14.600 watchers (because of high number of items in both trees). We experienced performance problems because of the component used, angular-ui-tree, and we reduced them opening the page with most of the tree collapsed.
We cannot change that component because it is the only one which features drag and drop between trees, but in another page where we had drag & drop between simple lists we have tried those two components: angular-dragdrop and angular-drag-and-drop-lists. The first had a lot of performance problems (with about 500 items) while the second run really really fast. In his documentation on github, section "Why another drag & drop library?" you can read why it is so fast and why the other is so slow.
So, I can speculate that third part's components bring you the real performance problems, and not the watchers.
In any case, we often write our watchers with a check like the one below to not run the code unless needed.
$scope.$watch('variableToWatch', function(newValue, oldValue) {
if (newValue === oldValue) {
return;
}
... watcher code ...
}
Another way to reduce watchers from html is using one-time-binding.
Example:
<div ng-if="::vm.user.loggedIn"></div>
Related to performance... - One pattern i came up with is to use a private object and assign the prototype of a function for easy access. then in any function ,controllers, directives...ect you can access the prototype of other function,controllers,directives easily. instead of using watchers you can use this pattern like a event loop. instead of angular running 300+ watchers every digest cycles. using this pattern only what triggers the function call matters.
An example of this pattern
var private = {} //accesable to entire app
var app = angular.module('some-app',[])
.controller('someCtrl',['$scope',someCtrl])
.directive('someDirective',someDirective);
function someCtrl($scope){
private.someCtrl = someCtrl.prototype
someCtrl.prototype.update = function(someDate){
//do something....
//access to someCtrl arguments
$scope.something = someDate
}
}
function someDirective(){
var someCtrlProto = private.someCtrl;
return{
link:function(scope ,elm ,attr){
elm[0].addEventListener('click',fucntion(){
someCtrlProto.update(someData)
});
//or
elm[0].addEventListener('click',someCtrlProto.update) //to trigger someCtrl.update from here
}
}
}
I'm very new to Angular (not Javascript), so I apologize if I don't use the correct terms/procedure.
I have a model like so:
BuyingGroups: {
availableBuyingGroups: [
obj1: {
ID: XX,
Title: XX
},
....
],
affiliatedBuyingGroups: [
obj1: {
ID: XX,
Title: XX
},
....
]
}
There are (2) panes (think of them as list boxes). A user can add an object from the left pane to the right, and vice versa. Basically I'm just moving objects from one group to the other.
On load, I take those (2) lists, and use angular.copy to copy those to another static list, so as not to affect the original (I read that angular uses the context if you don't use angular.copy).
On reset (cancel button), I reset the original lists back to the copies (to keep the original state). The problem is on the cancel button event, the list seems to "duplicate" itself for about .3 seconds, which flickers on the pane, then reverts back to it's original state (on page load).
I've attempted to clear out the array, set a timeout, etc, but nothing seems to have an effect. Is there (or should there be) a more efficient, better way of doing this? Perhaps I do not fully understand how angular binds to the $scope on changes?
self = $scope (fyi)
self.companyBuyingGroups = response.data.BuyingGroups;
self.staticCompanyBuyingGroupsModel = angular.copy(self.companyBuyingGroups);
self.staticCompanyAffiliationsModel = angular.copy(self.companyAffiliations);
HTML
<!-- affiliated instance -->
<li ng-repeat="group in companyBuyingGroups.affiliatedBuyingGroups">
.....
</li>
Reset button function
self.resetBuyingGroupsForm = function () {
self.companyBuyingGroups.affiliatedBuyingGroups = [];
self.companyBuyingGroups.availableBuyingGroups = [];
//setTimeout(function () {
self.companyBuyingGroups.affiliatedBuyingGroups = angular.copy(self.staticCompanyBuyingGroupsModel.affiliatedBuyingGroups);
self.companyBuyingGroups.availableBuyingGroups = angular.copy(self.staticCompanyBuyingGroupsModel.availableBuyingGroups);
//self.companyBuyingGroups = angular.copy(self.staticCompanyBuyingGroupsModel);
//}, 50)
}
EDIT
I have tried to clear the arrays prior to reset, along w/ not clearing them. Also my arrays are super small (less than 50 objects).
To explain more of the "flickering", on the cancel function, the right pane seems to add the copy (instead of using the original). Meaning, if the original list had 3 items, and I added an item from the left pane (making it 4), then the cancel function was called, the right pane shows 7 items for about .3s, then reverts back to 3 items (which was the original).
You should append track by group.ID to your ng-repeat expression, this will allow AngularJS to reuse the DOM when the list resets, which will fix the issue.
When you reset your list, Angular first needs to remove every item from the DOM (since they are not in the array anymore), then add them back. Using track by group.ID will allow ngRepeat to track them by ID instead of reference, and it now knows which item to remove and which to keep.
#1
It probably blinks because you first empty the arrays.
Try commenting out the first two assignments to =[] in you Reset function, and keep only two angular.copy assignments.
#2
If you have huge lists that might take time to be rerendered, consider comparing the arrays model in controller to the initial one - to find and keep those list items that are already rendered, and add/remove only the non-corresponding ones.
I'm trying to write some javascript that will stack objects by setting z-index.
Test Case:
http://christophermeyers.name/stacker/
I've hacked that together, but I'd like to extrapolate that behavior to something a little more logical. That is:
Given x number of elements, when element C is moved to the top, all elements above that element must move down 1, while all elements below that element should remain in place.
A "linked list" makes for a good data structure when you're doing this kind of thing. Keep track of the order of your stackable elements via a series of simple nodes..
// ListNode
{
value: {}
next: {<ListNode>}
}
..and update the sequence as new nodes are added or selected.
I have posted a working example of a list being used for depth sorting at the following URL:
http://aethermedia.net/sandbox/depth-sorting.html
Sorry I don't have time to pull up a more appropriate tutorial =/