AngularJS and jqueryUI apply new index of list elements - jquery-ui

I'm currently finishing a feature with list reordering and I'm stuck with a probably very simple thing, but I'm really sorry that I can't figure out how to solve it .... ( possibly my brain just ignores this logic :) )
The purpose is simple :
I have a list of items with a "position" data (different from $index).
I drag and drop items to change their order.
When the drag stops, all items in the list should have a new position, that'll be updated with a $resource object.
For example, after dragging I have this:
$index elem.position
0 2
1 1
2 3
should automatically change position 2->1, 1->2 and 3->3.
The problem :
With angularUI I can have the current item index but not the others in the list. So I can't change the whole list index after stopping the drag. And it's frustrating because on view, I can catch easily $index but not in controller.
Code :
in controller.js
$scope.updateSortable = {
stop: function(e, ui) {
for (var i=0; i<$scope.list.length; i++) {
var elem = $scope.list[i];
// here's don't know how to update elem.position
//elem.position = ui.item.index; // bad one, I know :)
//elem.$update();
}
},
placeholder: "xp-hightlight",
axis: 'y'
};
in html page :
<div ng-repeat="el in list">
<div>
<span class="position" ng-bind="el.position"></span>
</div>
</div>
The json items look like that :
{ id: 47, description: "my text in the list", position: 1}

Would this work for you, or do you have to have the position variable set?
<div ng-repeat="el in list">
<div>
<span class="position">{{$index + 1}}</span>
</div>
</div>
I added this to your controller 'testCtrl'. You can update the position element within the callback of this watch:
var _list;
$scope.$watch(function() {
return JSON.stringify($scope.items)
},function(_l) {
if(typeof _l !== 'undefined') {
_list = JSON.parse(_l);
console.log(_list)
}
});

I just solved the issue, and thanks to koolunix I managed the update of position directly inside the controller with this plunkr :
http://plnkr.co/edit/kDkNLSjoHbnaumk2uaOF?p=preview
The main fact was just to manage the position with the loop in list items.
elem.position=i+1;

Related

Free Text Entry in Angular Material mdAutoComplete

I want my angular material autocomplete to be a list of suggestions but not requirements. However I'm not sure how to implement as their is no clear example from the Angular Material docs.
In the example below my model is $ctrl.item.category
Clearly the example below is wrong, as my model is linked to md-selected-item, but this only works if I select an item. I want the user to be able to free enter the text if the item is not in the list. Basically how autocomplete already works in most browsers.
I see plenty of questions on how to disable this, but they are not trying to disable so much as clean up the left over text when an item is not selected. In these cases when an item is not selected then the model value is null, but text is left in the input.
I want the text left int he input to be the model value if the person does not select (or a match is not made).
md-autocomplete(
md-floating-label="Category Name"
flex="50"
md-input-name="category"
md-selected-item="$ctrl.item.category"
md-search-text="catSearch"
md-items="category in $ctrl.categories"
md-item-text="category"
md-min-length="0"
md-select-on-match=""
md-match-case-insensitive=""
required=""
)
md-item-template
span(md-highlight-text="catSearch" md-highlight-flags="^i") {{category}}
My options ($ctrl.categories) is an array of strings ['Food','Liqour'] and I wan the user to be able to use one of those or free enter Tables as their choice.
In this case you should link md-search-text to your model.
If you want to implement fuzzy search you have to write the filter method yourself. Look at this example:
template:
<md-autocomplete
md-items="item in $ctrl.itemsFilter()"
md-item-text="item.label"
md-search-text="$ctrl.query"
md-selected-item="$ctrl.selected"
>
<md-item-template>
<span md-highlight-text="$ctrl.query">{{item.label}}</span>
</md-item-template>
<md-not-found>
No item matching "{{$ctrl.query}}" were found.
</md-not-found>
<div ng-messages="$ctrl.myValidator($ctrl.query)">
<div ng-message="short">Min 2 characters</div>
<div ng-message="required">Required value</div>
</div>
</md-autocomplete>
controller:
var items = [ ... ];
ctrl.itemsFilter = function itemsFilter() {
return ctrl.query ? filterMyItems(ctrl.query) : items;
};
ctrl.myValidator = function (value) {
return {
short: value && value.length < 2,
required : value && value.length < 1,
};
};
then you just need to add filterMyItems method to filter your items
To improve the answer of #masitko, I have implemented the filter in a way, that it adds the query to the filtered list. So it becomes selectable and a valid option. So it's possible to make the autocomplete a suggestion box.
I'm using ES6 in my projects. But it should be easily adaptable to ES5 code.
myFilter() {
if (!this.query) return this.items;
const
query = this.query.toLowerCase(),
// filter items where the query is a substing
filtered = this.items.filter(item => {
if (!item) return false;
return item.toLowerCase().includes(query);
});
// add search query to filtered list, to make it selectable
// (only if no exact match).
if (filtered.length !== 1 || filtered[0].toLowerCase() !== query) {
filtered.push(this.query);
}
return filtered;
}

array observable with content observable and jqAutocomplete

I'm using Knockout 3 with the plugin jqAutocomplete by Ryan Niemeyer. I have a problem with this model:
var ViewModel = function() {
var self = this;
self.myOptionsObs = ko.observableArray([
{ id: ko.observable(1), name: ko.observable("item 1 o"), description: ko.observable("item label 1 o") },
{ id: ko.observable(2), name: ko.observable("item 2 o"), description: ko.observable("item label 2 o") },
{ id: ko.observable(3), name: ko.observable("item 3 o"), description: ko.observable("item label 3 o") }
]);
self.myValueObs = ko.observable();
};
ko.applyBindings(new ViewModel());
<input data-bind="jqAuto: { source: myOptionsObs, value: myValueObs, inputProp: 'name', template: 'itemTmpl' }" />
As you can see, there is an observable array and each element is also an observable.
The autocomplete don't work well. As you can see in this Fiddle, the left column has an observable array but its elements aren't observable. If you click in the left box and write something, a list of options appear.
But in the right column, you have the same, but the element's are all observable. If you click in the right box and write something, when the list appear, if you move the cursor up and down, you could see that the row 'name' gets deleted and filled with zeros.
What I have to change in my data-bind attribute?
This question is related with this question.
I have to say that this solution works ok for me. But the updated plugin don't.
Thanks !!
The jqAutoComplete plugin isn't setup to work with observable properties (although it could be enhanced to do so without much work).
For now, I think that your best bet is to create a computed that will always return a plain and up-to-date version of your options.
self.myOptionsObs.plain = ko.computed(function() {
return ko.toJS(self.myOptionsObs);
});
Sample: http://jsfiddle.net/rniemeyer/45cepL9b/
I'll try to take a look at some point about supporting observable properties. Shouldn't take many changes.

Calculating columns when upgrading to ui-grid 3.0 from ng-grid

I have a plunker to demonstrate my issues.
1 Issue: Totals column is blank
I need to calculate the columns (Total) so I have a $scope method as follows:
$scope.getTotal = function(a, b, c, d, e) {
return $filter('number')(Number(a) + Number(b) + Number(c) + Number(d) + Number(e), 2);
};
and the cellTemplate to make that call:
<div class="padd black">
{{getExternalScopes().getTotal(row.entity.M,row.entity.T,row.entity.W,row.entity.H,row.entity.F)}}
</div>
and in columnDefs, I use:
{
field: 'total',
displayName: 'Total',
enableColumnMenu: false,
type: 'number',
cellFilter: 'number:1',
cellClass: 'text-right',
headerCellClass: 'text-center',
cellTemplate: 'total.tmpl.html',
width: '10%'
}
[2] Issue: ng-class not working in template, pending.icons.html
We use icons to indicate status of a record and in the template, I am using a conditional for ng-class which should control the color of the icon but it is being ignored and all icons are colored black:
ng-class="{'cBlue': grid.getCellValue('vote_pending'), 'cGray': !grid.getCellValue('need_vote')}"
Uhh, this answer may only be a partial.
Since v3 the external scopes are (probably) gone and have been replaced by grid.appScope.function_xxx(). Read more here
So you have to leave out the external scopes part in your markup:
<div ui-grid="gridOptions" ui-grid-edit ui-grid-cellNav class="grid"></div>
The function for calculation in your scope should be:
$scope.getTotal=function(a, b, c, d, e) {
return a+b+c+d+e;
};
(I left the filtering and number stuff out)
And your cell template would now be:
<div class="padd black">
{{grid.appScope.getTotal(row.entity.M,row.entity.T,row.entity.W,row.entity.H,row.entity.F)}}
</div>
This seems to work here: Plunker
Since I'm on my way home now, I'll check for your 2nd Issue later.
Hope this helps so far.
Update:
2nd Issue:
Use this in pending.icons.html :
ng-class="{'cBlue': row.entity.vote_pending, 'cGray': !row.entity.vote_pending}"
Works for me (allthough my Plunker is very, veeery slow at the moment)
Look here

Using bootstrap toggle with Awesome MVC Grid

I'm trying to incorporate a bootstrap toggle into a column of my AwesomeMVC Grid (http://demo.aspnetawesome.com/GridDemo) but the toggle doesn't get rendered correctly even after initializing the bootstrap toggle. This is because the AwesomeMVC grid gets rendered after page load.
I don't want to implement a timeout in initializing the bootstrap toggles as Grid loading times may be different.
Has anyone tried implementing any similar bootstrap plugin with AwesomeMVC?
Here's my code sample.
View
#(Html.Awe().Grid("UserList")
.Url(Url.Action("GetUser", "User"))
.Persistence(Persistence.Local)
.Selectable(SelectionType.None)
.Parent("pagesize", "pagesize")
.Columns(
new Column { Name = "Name", Header = "FullName", Width = 72, SortRank = 1, Sort = Sort.Asc },
new Column { Name = "Active", Header = "Active", Width = 60, ClientFormatFunc = "CustomActive", Group = true}
))
Javascript
//Custom content for Active ClientFormatFunc
var CustomActive = function (GetUserList_Result) {
return "<input type=\"checkbox\" class=\"checkbox-toggle\" data-toggle=\"toggle\" data-on=\"Yes\" data-off=\"No\" data-size=\"small\">";
}
$(function () {
$(".checkbox-toggle").bootstrapToggle();
});
there's the aweload event that you can hook up to, also when you render the checkbox I think you need to set its checked state, so you should end up with something like this:
#(Html.Awe().Grid("UserList")
.Url(Url.Action("GetUser", "User"))
.Persistence(Persistence.Local)
.Parent("pagesize", "pagesize")
.Columns(
new Column { Name = "Name", Header = "FullName"},
new Column { ClientFormatFunc = "CustomActive", Width = 75}))
<script type="text/javascript">
$(function () {
$('#UserList').on('aweload', function () {
$(this).find(".checkbox-toggle").bootstrapToggle();
});
});
//Custom content for Active ClientFormatFunc
var CustomActive = function (model) {
var checked = model.Active ? 'checked = "checked"' : '';
return '<input type="checkbox" ' + checked + ' class="checkbox-toggle" data-toggle="toggle" data-on="Yes" data-off="No" data-size="small">';
};
<script>
I also removed .Selectable(SelectionType.None) because it is like that by default, and also I see that you group by a boolean column (active) not sure if that makes sense, it will just split your grid in 2 groups, Column.Name is not required, it is used for binding to model for sorting/grouping, and it's better to always have at least 1 column without width (if you set width to all columns and the grid width doesn't match they work as percentages)

Display index of list item using jQuery

I have an unordered list like so:
<ul class="foo">
<li id="asdf">
<span class="indexnumber"></span>
<span class="description">whatever</span>
</li>
<li id="asdfasdf">
<span class="indexnumber"></span>
<span class="description">whatever</span>
</li>
</ul>
I want to display the index number of the list item in the indexnumber span (i.e., the first item will display 1, the second will display 2, and so on). My users will have the ability to sort the list items (using jquery ui sortable). I would like to be able to show my users a number in the list item that indicates where the list item is (and the value can change as the user re-orders the list).
How can I simply display the index position in the array using jquery? Alternately, is there an easy way to do this with the sortable events (i.e., once the list is sorted, increment or decrement the indexnumbers in all other list items as appropriate)?
I guess you could add the DOM elements to an array using something like
var arr = jQuery.makeArray(document.getElementsByTagName("span"));
Then get their index using jQuery.inArray( value, array ) which returns an int.
Not very pretty though
I ended up doing the following:
$(function() {
$(".foo").sortable({
stop: function(event, ui) {
var itemID = $(ui.item).attr("id");
var items = $("li#" + itemID).parent(".foo").children();
var updateditems = new Array();
for (var i = 0; i <= items.length - 1; i++) {
var singleitemID = $(items[i]).attr("id");
var loc = i + 1;
$("#" + singleitemID).text(loc);
updateditems.push([singleitemID, loc]);
}
}
});
});
index_list = function() {
$(".foo li").each(function(i){
$(this).find(".indexNumber").html((i+1));
});
}
you can call this function on document ready and on the change attribute of the sortable object

Resources