I'm a beginner in Vue.js and I'm stuck at this:
<span>Condition</span>
<el-select style="width: 94%;" v-model="condition[index+1]">
<el-option label="Ambient" value="Ambient">Ambient</el-option>
<el-option label="Frozen" value="Frozen">Frozen</el-option>
</el-select>
<h4>
<el-checkbox v-model="check[index+1]" #change="selectAllPanels(index+1, item.id)">Select All</el-checkbox>
</h4>
<div class="card-content panels-category" v-for="(category, n) in panelsCategory[item.id]" :key="category.id" style="float: left">
<div class="panel-category" v-if="searchList[item.id][n].length > 0">
<el-checkbox v-model="checkAll[index+1][item.id][n]" #change="checkAllPanel(index+1, item.id, n)" :label="category.id" :key="category.id">
<h5 class="card-title">{{ category[0].panel_category }}</h5>
</el-checkbox>
</div>
<div class="panels" v-for="panels in searchList[item.id][n]" :key="panels.id"> <!-- in panelsCategory[item.id][n]-->
<!--<el-checkbox-group class="panels-name" v-model="selectedPanels[index+1][item.id]" #change="handelAddSample(index+1)">-->
<el-checkbox :checked="checkAll[index+1][item.id][n]" class="panels-name" v-model="selectedPanels[index+1][item.id]" #change="handelAddSample()" :label="panels.id" :key="panels.id" >{{panels.reportable_name}}</el-checkbox>
<el-popover class="panel-popover"
trigger="hover"
#show="getPanelTest(panels.id)"
placement="bottom">
<el-button slot="reference" class="ti-help popover-help"></el-button>
<slot>{{panelTest}}</slot>
</el-popover>
<!--</el-checkbox-group>-->
</div>
<div class="panels" v-if="searchList[item.id][n].length > 0" v-for="i in getPanelsLength(index+1, item.id, n)" ></div>
</div>
At click on plus button from the right side-->click sth from the dropdown list-->it displays the new tab
So, here, in these tabs comes the magic: the checkboxes are not working well, I mean at click on "Select All" checkbox, all checkboxes should be selected, but this doesn't happen until one subcategory checkbox is clicked (like the third child), but the list where the selected checkboxes are stored is populated well.
I tried this.$forceUpdate() and even this.$set() inside the functions called at click on select All and category title (those with blue background)
Can somebody please help?
I tried to reproduce it in this fiddle Here.
<el-checkbox class="panels-name"
:value="!!selectedPanels[index+1][item.id].find(i=>i===panels.id)"
#change="handelAddSample(index+1,item.id,panels.id)"
:label="panels.id"
:key="panels.id"
>{{panels.reportable_name}}
</el-checkbox>
bind by :value="!!selectedPanels[index+1][item.id].find(i=>i===panels.id)", it worked.
just add it to child
handelAddSample(a,b,id) {
let m = -1;
if(!!this.selectedPanels[a][b].find((i,index)=>{if(i===id){m=index;return true}})){
this.selectedPanels[a][b].splice(m,1);
}else {
this.selectedPanels[a][b].push(id);
}
this.$forceUpdate()
},
new demo
The issue appears to be with the chekbox
<el-checkbox
:checked="checkAll[index+1][item.id][n]"
class="panels-name"
v-model="selectedPanels[index+1][item.id]"
#change="handelAddSample()"
:label="panels.id" :key="panels.id"
>{{panels.reportable_name}}</el-checkbox>
this element has both :checked and v-model defined, which will compete, more importantly though, you're using model on a static data (updating searchList, instead of checkAll). For an element-ui you only use v-model, and pass the checkAll instead of searchList.
<el-checkbox
v-model="checkAll[index+1][item.id][n]"
class="panels-name"
#change="handelAddSample()"
:label="panels.id" :key="panels.id"
>{{panels.reportable_name}}</el-checkbox>
another thing you should do is use this.$set when you're updating nested properties
selectAllPanels(index, item) {
if (this.check[index] === true) {
this.$set(this.check, index, true) // <--- note use of $set here
} else {
this.$set(this.check, index, false) // <--- here
}
for (let i = 0; i <= this.panelsCategory[item].length; i++) {
console.log(this.panelsCategory[item][i])
if (this.panelsCategory[item][i] !== undefined && this.check[index]) {
this.$set(this.checkAll[index][item], i, true) // <--- here
this.checkAllPanel(index, item, i)
} else {
if (this.panelsCategory[item][i] !== undefined) {
this.$set(this.checkAll[index][item], i, false) // <--- and here
this.checkAllPanel(index, item, i)
}
}
}
//this.$forceUpdate() // <--- then you don't need forceUpdate
},
Related
I am new to angular js. In my code user changes the value of radio buttons. And depending on the value of the selected radio button, a piece of code is loaded from the ng-switch
HTML:
<body ng-app="">
<div ng-repeat="button in modes">
<label>
<input type="radio" ng-model="data.mode" value="{{button.value}}" ng-click="clearObjectIdModal()" name="e_modes">
{button.label}}
</label>
</div>
<div ng-switch on="data.mode">
<div ng-switch-when="client">
<label for="e_selected_object_item_id">Select Client name: </label>
<select id="e_selected_object_item_id" name="e_selected_object_item_id" ng-model="currentDataItem.object_id" required>
<option ng-repeat="item in customersListArr" value="{{ item.id }}">{{ item.Name }}</option>
</select>
</div>
<div ng-switch-when="agent">
// This part is similar to the previous one
</div>
</div>
</body>
Controller part:
$scope.data = {};
$scope.setFile = function () {
if ($scope.data.mode == 'client')
return 'client';
else if ($scope.data.mode == 'agent')
return 'agent';
$scope.modes = [{
value: 'client',
label: 'Client'
},{
value: 'agent',
label: 'Agent'
}];
$scope.currentDataItem = data; // data is preloaded from inputs in form
There is also a ng-click="clearObjectIdModal()" that clears the model when switching radio buttons:
$scope.clearObjectIdModal = function() {
$scope.currentDataItem = "";
}
The problem is that every time when the radio button is switched to the select value, which dynamically changes, the value of the first option in it becomes equal to undefined. Because in the array from where these options are built there is no such object_id (This is the id that is not there, so an empty field is drawn).
That is, there are all works. But the first option in the select(after switching to another radio button) is rendered as an empty string.
There are thoughts, how it can be fixed?
I'm not sure if I understand you problem correctly but I would suggest a few improvements.
change your setFile function to as follows
$scope.setFile = function (){return $scope.data.mode;}
I also do not see the closing brackets for your function in your code. Besides if your function will only return the data.mode then why need the function?
I would suggest initialize your data object properly like:
$scope.data = {mode:'client'};
Change your clearObjectIdModal function as:
$scope.clearObjectIdModal = function(mode)
{
$scope.currentDataItem = "";
$scope.data.mode=mode;
}
and in your HTML use it as ng-click="clearObjectIdModal(button.mode)"
So in function clearObjectIdModal() I wrote:
$scope.clearObjectIdModal = function() {
if ($scope.e_data["mode"] == 'client') {
if ($scope.customersListArr.length > 0) {
$scope.currentDataItem.object_id = $scope.customersListArr[0]['id'];
}
}
else if ($scope.e_data["mode"] == 'agent') {
if ($scope.agentsListArr.length > 0) {
$scope.currentDataItem.object_id = $scope.agentsListArr[0]['id'];
}
}
}
And after this when I change radio buttons the first option in current select(which every time is changed) will be not empty.
Also the problem with an additional empty option is possible to solve when you add a title as the first item in the list:
<option value="" disabled>Select</option>
I'm having an issue with ngRepeat :
I want to display a list of students in two different ways. In the first one they are filtered by group, and in the second they are not filtered.
The whole display being quite complex, I use a ngInclude with a template to display each student. I can switch between view by changing bClasseVue, each switch being followed by a $scope.$apply().
<div ng-if="currentCours.classesOfGroup !== undefined"
ng-show="bClassesVue">
<div ng-repeat="group in currentCours.classesOfGroup">
<br>
<h2>Classe : [[group.name]]</h2>
<div class="list-view">
<div class="twelve cell"
ng-repeat="eleve in group.eleves | orderBy:'lastName'"
ng-include="'liste_eleves.html'">
</div>
</div>
</div>
</div>
<div class="list-view" ng-show="!bClassesVue">
<div class="twelve cell"
ng-repeat="eleve in currentCours.eleves.all"
ng-include="'liste_eleves.html'">
</div>
</div>
My problem happens when my list of students change (currentCours here). Instead of refreshing the ngRepeat, both lists concatenate, but only in the unfiltered view.
I tried adding some $scope.$apply in strategic places (and I synchronize my list for example) but it doesn't help.
EDIT : the function used to refresh currentCours in the controller. It's called when a "cours" is selected inside a menu.
$scope.selectCours = function (cours) {
$scope.bClassesVue = false;
$scope.currentCours = cours;
$scope.currentCours.eleves.sync().then(() => {
if ($scope.currentCours.classe.type_groupe === 1) {
let _elevesByGroup = _.groupBy($scope.currentCours.eleves.all, function (oEleve) {
return oEleve.className;
});
$scope.currentCours.classesOfGroup = [];
for(let group in _elevesByGroup) {
$scope.currentCours.classesOfGroup.push({
name: group,
eleves: _elevesByGroup[group]
});
}
$scope.bClassesVue = true;
}
});
utils.safeApply($scope);
};
Well, I found a workaround, but I still don't know why it didn't work, so if someone could write an explanation, I would be very thankful.
My solution was simply to open and close the template each time I switch between views.
I faced with strange behaviour of uib-collapse.
Let's assume I have a list of elements and i want each of them to be collapsed. Also i want to refresh its content periodically depend on something.
For example: i have some items and each of them have description which consists of some sections. I can pick item and description sections should be populated with item's description content. The problem is that each time i refresh its content, some sections are collapsing (despite the fact i set uib-collapse to false)
My controller:
var i = 0;
$scope.sections = [0,1,2];
$scope.next = function(nextOffset) {
i+=nextOffset;
$scope.sections = [i, i+1, i+2]
}
My template:
<button ng-click="next(1)" style="margin-bottom: 10px">Next item</button>
<button ng-click="next(2)" style="margin-bottom: 10px">Next next item</button>
<button ng-click="next(3)" style="margin-bottom: 10px">Next next next item</button>
<div ng-repeat="section in sections">
<div uib-collapse="false">
<div class="well well-lg">{{ section }}</div>
</div>
</div>
So when i click first button, only one section does transition. When i click second, 2 section do transition and click to third button leads to all section transition.
See plunkr
Any ideas?
UPD: if $scope.sections is array of object, not of primitives, then all sections have transition in each of 3 cases. It is so ugly...
You are not refreshing the existing content, you are adding new arrays each time, which will make ng-repeat remove the old DOM elements and insert new ones.
If you try with track by $index you will see the difference:
<div ng-repeat="section in primitiveSections track by $index">
Demo: http://plnkr.co/edit/hTsVBrRLa8nWXhaqfhVK?p=preview
Note that track by $index might not be the solution you want in your real application, I just used it for demonstration purposes.
What you probably need is to just modify the existing objects in the array.
For example:
$scope.nextObject = function(nextOffset) {
j += nextOffset;
$scope.objectSections.forEach(function (o, i) {
o.content = j + i;
});
};
Demo: http://plnkr.co/edit/STxy1lAUGnyxmKL7jYJH?p=preview
Update
From the collapse source code:
scope.$watch(attrs.uibCollapse, function(shouldCollapse) {
if (shouldCollapse) {
collapse();
} else {
expand();
}
});
When a new item is added the watch listener will execute, shouldCollapse will always be false in your case so it will execute the expand function.
The expand function will always perform the animation:
function expand() {
element.removeClass('collapse')
.addClass('collapsing')
.attr('aria-expanded', true)
.attr('aria-hidden', false);
if ($animateCss) {
$animateCss(element, {
addClass: 'in',
easing: 'ease',
to: {
height: element[0].scrollHeight + 'px'
}
}).start().finally(expandDone);
} else {
$animate.addClass(element, 'in', {
to: {
height: element[0].scrollHeight + 'px'
}
}).then(expandDone);
}
}
If this is the intended behavior or not I don't know, but this is the reason why it happens.
this is a comment on the original ui-bootstrap library: (and the new uib prefixed directive doesn't comply this comment.)
// IMPORTANT: The height must be set before adding "collapsing" class.
Otherwise, the browser attempts to animate from height 0 (in
collapsing class) to the given height here.
use the deprecated "collapse" directive instead of new "uib-collapse" until it gets fixed.
I have a list of 1000+ items which I display using NgRepeat in Angular 1.3. The list populates with buttons. I have noticed significant delay on the click event int he list once it grows in size. When the list is only 5-10 items the clicks are instant. When the list is 1000 there is about 2-5 second delay before the button clicks are actually processed.
Now I cannot tell if this is a browser issue, but I suspect it has to do with too many listeners being used somewhere, causing the browser to check for them.
Here is sample of code in case there is a culprit hiding in there:
<div id="side" class="animated" style="min-height: 250px;"
data-ng-class="{'fadeInRight':documentDone}" data-ng-style="settings.listCss">
<div class="col-md-12 text-center" data-ng-style="settings.listCss"><h4>{{label}}</h4> {{inSide}} </div>
<div data-ng-repeat="doc in ::documents track by $index" id="{{ ::doc.id }}"
class="document ng-hide" data-ng-show="doc.show"
data-ng-init="docSettings = (settingslist[doc.companyid] || settings)" data-ng-style="::docSettings.listCss">
<div class="col-md-12" data-ng-style="docSettings.listCss">
<h4>
<span>{{ ::$index + 1 }}</span>
<span class="title-in-clusters">
{{ ::doc.title }}
<button type="button"
class="btn btn-primary btn-xs"
data-ng-click="viewJob(doc, docSettings)"
data-ng-style="docSettings.buttonCss">
<strong>VIEW</strong>
</button>
<a href="{{ ::doc.joburl }}" class="apply" target="_blank">
<button type="button" class="btn btn-primary btn-xs" data-ng-click="apply(doc.jobid, doc.companyid)"
data-ng-style="docSettings.buttonCss">
<strong>APPLY</strong>
</button>
</a>
</span>
</h4>
</div>
<div class="col-md-12" data-ng-style="docSettings.listCss">
<span class=""><strong>ID: </strong>{{ ::doc.jobid }}</span>
<img data-ng-if="docSettings.heading.logourl && docSettings.heading.logourl != ''"
data-ng-src="{{docSettings.heading.logourl}}" class="side-logo inline-block" id="">
</div>
<div class="col-md-12" data-ng-style="docSettings.listCss">
<strong>Location: </strong><span class="">{{ ::doc.location }}</span>
</div>
<div class="col-md-12" data-ng-style="docSettings.listCss">
<strong>Updated Date: </strong><span class="">{{ ::doc.updateddate }}</span>
</div>
<div class="col-md-12" data-ng-style="docSettings.listCss">
<hr data-ng-style="docSettings.listCss">
</div>
</div>
</div>
There is nothing offensive about the other functions that are called when the button is pressed.
var modalInstance;
$scope.viewJob = function(modalDoc, docSettings) {
$scope.modalDoc = modalDoc;
$scope.docSettings = docSettings;
//the trusAsHtml takes string creates an object, so this will in essence convert string to object
//make sure you check if it is a string since it could be called multiple times by user (close and reopen same modal)
if (modalDoc.overview && typeof modalDoc.overview === 'string') {
$scope.modalDoc.overview = $sce.trustAsHtml(modalDoc.overview);
}
if (modalDoc.qualifications && typeof modalDoc.qualifications === 'string') {
$scope.modalDoc.qualifications = $sce.trustAsHtml(modalDoc.qualifications);
}
if (modalDoc.responsibilities && typeof modalDoc.responsibilities === 'string') {
$scope.modalDoc.responsibilities = $sce.trustAsHtml(modalDoc.responsibilities);
}
modalInstance = $modal.open({
templateUrl: 'app/modal/job_preview.html',
//templateUrl: 'myModalContent.html',
scope: $scope
});
};
I want to optimize this code so it can sever a list of up to 1500, but I cannot for the life of me find the culprit.
I will also take any solutions to reduce the load instead. Like for now I am thinking I may limit the number of DOM elements to 10 to so, and have angular rotate what is being viewed as user scrolls if it will result in better UX.
UPDATE:
Many things have been tried, from use of bind-once to more convoluted solutions that retard some of the watchers Which are enat but require a lot of Math to estimate which items are visible etc.
I finally decided on one solution that was easiest to do: I made a list of only items I wish shown and on mouse scroll up or down I edit the list.
First part of the solution is use of two directives:
.directive('ngMouseWheelUp', function() {
return function($scope, $element, $attrs) {
$element.bind("DOMMouseScroll mousewheel onmousewheel",
function(event) {
// cross-browser wheel delta
var event = window.event || event; // old IE support
var delta = Math.max(-1, Math.min(1, (event.wheelDelta || -event.detail)));
if(delta > 0) {
$scope.$apply(function(){
$scope.$eval($attrs.ngMouseWheelUp);
});
// for IE
event.returnValue = false;
// for Chrome and Firefox
if(event.preventDefault) {
event.preventDefault();
}
}
});
};
})
.directive('ngMouseWheelDown', function() {
return function($scope, $element, $attrs) {
$element.bind("DOMMouseScroll mousewheel onmousewheel", function(event) {
// cross-browser wheel delta
var event = window.event || event; // old IE support
var delta = Math.max(-1, Math.min(1, (event.wheelDelta || -event.detail)));
//console.log(event);
if(delta < 0) {
$scope.$apply(function(){
$scope.$eval($attrs.ngMouseWheelDown);
});
// for IE
event.returnValue = false;
// for Chrome and Firefox
if(event.preventDefault) {
event.preventDefault();
}
}
});
};
})
These two enable me to disable scrolling in the list on the right side. Then I would create two additional arrays from the documents in routeScope. First list would be generated whenever the documents were updated (which was an event listener for event emitted by the UI of the right hand side graph), this filter would only return array members that had the show property set to true:
var showFilter = function(object) {
return object.show;
}
This would be my array of visible items. From this array I created another Array of shown items. I defined a constant for max size of 7, so at most there are 7 items shown. And of course I set overflow of the parent container to none to disable scrollbar. (I may add a scroll graphic so the user knows he can scroll this field later)
Then I added the following directives to the side div:
data-ng-mouse-wheel-up="listUp()" data-ng-mouse-wheel-down="listDown()"
And inside the controller I defined listUp and listDown to work off an index and the max size constant to figure out which elements from the visible list I should add to the front or the back of the shown list.
/**
* Simulate scrolling up of list by removing bottom element and adding to top
*/
$scope.listUp = function() {
$rootScope.shownDocuments.unshift(getPrev());
$rootScope.shownDocuments.pop();
}
/**
* Simulate scrolling down of list by removing top element and adding to bottom
*/
$scope.listDown = function() {
$rootScope.shownDocuments.push(getNext());
$rootScope.shownDocuments.shift();
}
/**
* return next item in visibleDocuments array
*/
var getNext = function() {
$rootScope.topIndex++;
if ($rootScope.topIndex > $rootScope.visibleDocuments.length) {
$rootScope.topIndex -= $rootScope.visibleDocuments.length;
}
return ($rootScope.visibleDocuments[($rootScope.topIndex+max_shown_size)%$rootScope.visibleDocuments.length]);
}
/**
* Return previous item in visibleDocuments array
*/
var getPrev = function() {
$rootScope.topIndex--;
if ($rootScope.topIndex < 0) {
$rootScope.topIndex += $rootScope.visibleDocuments.length;
}
return ($rootScope.visibleDocuments[$scope.topIndex]);
}
Use of rootScope vs scope is mostly because modals would cause some undesirable behaviors if they were dismissed improperly.
Finally a reset function for the view:
/**
* Resets the list of documents in the visibleList (IE which are visible to client)
*/
var updateVisibleDocuments = function() {
$rootScope.topIndex = 0;
$rootScope.visibleDocuments = $rootScope.documents.filter(showFilter);
//clear view
$rootScope.shownDocuments = [];
$rootScope.topIndex = 0;
for (var i = 0; i < max_shown_size; i++) {
$rootScope.shownDocuments.push(getNext());
}
$rootScope.topIndex = 0;
}
This solution works really well because I only render 7 items even if my list has 100k items. This limits number of watchers tremendously.
You may want to try paginating to reduce the amount of things angular and the browser need to deal with on screen at any one time.
We're trying to implement a checkbox and list with the following functionality:
Clicking the checkbox will either clear the array if there are items in there, or add a new item if not.
Remove an item from the array when clicking the Remove button, once the last item is removed the checkbox automatically unchecks itself.
The problem I am having is that if you click to remove each array item, then click the checkbox to add a blank entry, I'm expecting the checkbox to be checked again (as per the checked observable), however it is not?
I have the following code:
http://jsfiddle.net/UBsW5/3/
<div>
<input type="checkbox" data-bind="checked: PreviousSurnames().length > 0, click: $root.PreviousSurnames_Click" />Previous Surname(s)?
</div>
<div data-bind="foreach: PreviousSurnames">
<div>
<input type="text" data-bind="value: $data">
<span data-bind="click: $root.removePreviousSurname">Remove</span>
</div>
</div>
var myViewModelExample = function () {
var self = this;
self.PreviousSurnames = ko.observableArray(['SURNAME1', 'SURNAME2', 'SURNAME3']);
self.removePreviousSurname = function (surname) {
self.PreviousSurnames.remove(surname);
};
self.PreviousSurnames_Click = function () {
if (self.PreviousSurnames().length === 0) {
self.PreviousSurnames.push('');
}
else {
self.PreviousSurnames.removeAll();
}
alet(2)
}
}
ko.applyBindings(new myViewModelExample());
If you are using together the click and the checked then you need to return true from your click handler to allow the browser default click action which is in this case checking the checkbox:
self.PreviousSurnames_Click = function () {
if (self.PreviousSurnames().length === 0) {
self.PreviousSurnames.push('');
}
else {
self.PreviousSurnames.removeAll();
}
return true;
}
Demo JSFiddle.
You need to use a computed to monitor the length of the observable array. This way when the length reaches zero you can react to it automatically.
self.surnames = ko.computed(function() {
var checked = true;
if (self.PreviousSurnames().length === 0) {
self.PreviousSurnames.push('');
checked = false;
}
return checked;
});
Now you will have the blank text box when all of the names are cleared. If you update your binding on the checkbox it will function properly as well.
<input type="checkbox" data-bind="checked: surnames, click: PreviousSurnames_Click" />Previous Surname(s)?
FIDDLE