How can I fill select2 data dynamically using angularjs - angularjs

My case is that I want to use the createSearchChoice feature of the Select2 widget. So I found out I need to use an html input element instead of a select element and so I cannot use ng-repeat to populate the select2 widget. I find out there is a 'data' option and have been able to populate the select2 widget with static data, but not when I've tried to fill it dynamically.
What works:
html:
<input class='select2' ui-select2='sel2props' type='hidden'>
in the controller:
$scope.sel2props = {
createSearchChoice: ...
data: [
{ id: 0, text: 'yabba' }
etc
]
};
But if I try to set data to a variable which I can then set to whatever the database feeds me the widget isn't populated.
data: $scope.data;
function to retrieve data {
$scope.data = retrieved data;
}
the retrieved data is exactly in the way specified.
If i set up a button to append the data key it will work:
$scope.appenddata = function () {
$scope.data.push({id:1, text: 'anot'});
};
So I'm thinking it's a timing issue and I try $digest and $apply but they don't work in controllers. I tried to set up a directive and actually can do simple widgets, but not select2, so I was hoping not to go down that path, well that is to say I went down that path and drowned. If anyone could help out that would be great.

The solution is straight forward. Just push the elements onto the select2 data array rather than referencing another array.

function (result) {
$scope.lookupOptions.data.length = 0; // remove old items
angular.extend($scope.lookupOptions.data, result.data); // add new items
}

A trick I've recently made use of is to use Select2's query option to pass in your latest data on demand.
I've put together an example, wrapped in a custom directive. See this Plunk.

Related

Creating <td> directive with Angular 1.5

I am trying to make an Angular 1.5 directive work within an ng-repeat for a table. A few things I need this to do (and please, if I am going at this the completely wrong way, let me know):
1) Update the html whenever new results are provided. Most importantly, the link function gets called once, with an empty result, and never called again. So, my table is blank. Always.
2) Inside the 'template' of the directive, I reference the controller's name and a method which is type coupling. Is the 'right' way to do this?
Here's my code snippets...
module-a.view.html:
<table>
<tr ng-repeat="(ii,result) in moduleACtrl.results">
<td>This is a normal TD</td>
<td-result result="{{result}}" ctrlName="moduleACtrl"></td-result>
</tr>
</table>
UPDATED
common.td-result.directive.js
(function(){
'use strict';
/* global angular */// ESLINT
angular.module('common').directive('tdResult',Directive);
Directive.$inject = ['$compile'];
function Directive($compile){
return {
restrict : 'E',
scope : {
result : '#',
ctrlName : '#'
},
template: '<td>{{result.prop1}}</td>\
<td>{{result.prop2}}</td>\
<td>{{result.prop3}}</td>\
<td><div ng-click="ctrlName.doSomething()">Something Goes Here {{ctrlName}}</div></td>'
};
}
})();
After reading a number of related questions on StackOverflow, and re-reviewing Angular's Directives documentation, I cannot figure out how to do what I want.
The overall context that got me here is that I have an OpenLayers 3 map, displaying result data from a query. I have the result data on the map interactively displaying result data when you click on it, like this: http://openlayers.org/en/latest/examples/getfeatureinfo-image.html
The difference though, is that I am using Angular 1.5. I have a ModuleA with a controller and view that house the map. I have another ModuleCommon that houses a service that handles creation of the map object and hands it to ModuleA.controller (or any controller who wants the map service). So, now, I have to create an interface for the map service that can generically handle displaying data to any client who wants to render said data.
I decided this was all a bad idea and that I need to step way back. First, the map object should not be responsible for displaying the data. The map is responsible for handling the 'singleclick' event and getting me a feature.
So, I decided that the controller, which instantiates the map object via the map service, will simply be responsible for creating and sending a callback to the map object so the map object knows what it's client wants on a singleclick. The getFeature is the map object's responsibility, and what to do with the feature's id is the client's (i.e. controller's) responsibility.
Whew!
Map code:
map.on('singleclick',function(event){
map.forEachFeatureAtPixel(event.pixel,function(feature){
callback(feature.getId());
});
});
Controller Code:
...
function mapCallback(someId){
var elem = angular.element('#info');
if ( someId === undefined ) {
elem.slideUp(); // Hide element
} else {
for (var data, ii = 0, len = vm.dataList.length; ii < len; ii++) {
data = vm.dataList[ii];
if ( someId === data.property ) {
vm.currentData = data;
$scope.$apply();
elem.slideDown(); // Show element
break;
} else {
elem.slideUp(); // Hide element
}
}
}
}
...
1) The Map is de-coupled from the data pertaining to the feature.
2) The Controller controls what happens when the map selects its data.
3) The HTML stays in the Controller's associated Component's templateUrl where it belongs!

How to design angular app with multiple models and select fields

What I'm trying to build is a SPA containing multiple selects on one page, where each select has it's own model. Choosing different options in the select boxes may show/hide other fields on the page and modify data for other selects (load different sets). Finally options in two selects may change the url (so when page is loaded with a proper address, those two options will be preselected).
Now I'm wondering what'll be the best approach here.
First. Is it worth to switch to ui-router in this case ?
Second. I need to write a custom directive for this select, that will have the following functionality
load data collection
display data and remember selection
reload it's data collection when other select triggered it
reload data for other selects
Now I've written directives before but never anything this (I think) complex. That's why few questions come to my mind.
How can I bind different data to my directive ?
Should this be just a single massive complex directive or divide it to smaller parts (like one directive for showing closed select box and another one to show the list and so on) ?
How can I trigger event when data should be changed and listen to a similar event from another select. Via controller ?
Thanks in advance for all your help.
What I can suggest is keep it MVC. Here is one of the solution -
Create a controller to store model data (here $scope.selectOptions inside controller)
Pass this values to 'select directive' instance to display
Whenever user select the value in directive, pass that selected value to controller (lets say in controller $scope.selectedValue is holding that value)
In controller add $watch on $scope.selectedValue and in its callback call for different data set for another select option. (lets say storing that data set in $scope.anotherSelectOption )
Pass this $scope.anotherSelectOption to '2nd directive' instance
Whenever user select the value in 2nd directive, pass that selected value to controller (lets say in controller $scope.anotherSelectedValue is holding that value)
In controller add $watch on $scope.anotherSelectedValue and in its callback change url thing you want to do
Here's your HTML will look like -
<div ng-controller="MyCtrl">
<select-directive selection-option="selectOptions", selected-option="selectedValue"> </select-directive>
<select-directive selection-option="anotherSelectOptions", selected-option="anotherSelectedValue"> </select-directive>
</div>
Here's your controller, it will look something like this -
yourApp.controller('MyCtrl', function($scope) {
$scope.selectOptions = []; // add init objects for select
$scope.selectedValue = null;
$scope.$watch('selectedValue', function() {
// here load new values for $scope.anotherSelectOptions
$scope.anotherSelectOptions = [] // add init objects for select
$scope.anotherSelectedValue = null;
});
$scope.$watch('anotherSelectedValue', function() {
// here add code for change URL
});
});
Here's your directive scope is
yourApp.directive('selectDirective', function() {
return {
templateUrl: 'views/directives/select.html',
restrict: 'E',
scope: {
selectionOption: '=',
selectedOption: '='
}
};
});

How does angularJs keep filter so that it is still enable when history back

Just like the example of angularJs: http://angular.github.io/angular-phonecat/step-12/app/#/phones, the filter could not be kept in angularJs, so after getting filter results, and then click one of the mobile to see the detail, and then click back of browser, the filter will disappear. I am wondering about if there is any way we could use to keep the filter?
Thanks Zhang, but seems it is a jQuery way. I've found the solution using angularJs. Simply put, store the filter in $rootScope when you type the search string, and then set it back when loading the view. See codes below:
// controller
function xxxController($rootScope, $scope) {
//set saved search string back to model
$scope.SearchModel = $rootScope.searchString;
$scope.saveSearchString() = function() {
$rootScope.searchString = $scope.SearchModel;
}
// view
<input type="text" ng-change="saveSearchString()" ng-model="SearchModel">
Maybe you want to clear the saved search string when clicking some link, you could reset $rootScope.searchString to empty. e.g.
// another controller
function otherController($rootScope, $scope) {
$scope.resetContext = function() {
$rootScope.searchString = "";
}
}
// view
clickme
Hope helpful to somebody.
You can temp the search field value:
var sTemp =$('#search').val();
and when you back:
if(!sTemp){
$('#search').val(sTemp);
$('#search').trigger('change');
}
Not tested it, just a approaching to achieve this

Using ng-grid with angularFire - key naming issues

I've played around with this a bit now, but can't get my head around it:
(Using angular 1.2.2, angularFire 0.5.0 and latest ng-grid)
So I have a firebase and I'm using angularFire to extract my data into an ng-grid component like this:
//First I go get my data, in this case something called 'grades' from ..../db/grades
var promise = angularFire(new Firebase($scope.fireBaseUrl), $scope, 'grades');
//When they're fetched I start up a watcher
promise.then(function(grades) {
startWatchGrade($scope, filterFilter, appSettings);
});
//Then I bind that grades as a datasource to my ng-grid
$scope.gridOptions = {
data: 'grades',
enableCellSelection: false,
enableRowSelection: true,
etc....,
Works great and I've done controls that add new items to 'grades' (push) and remove items (splice) and everything gets reflected to the ng-grid quite nicely. Like so:
//Adding new like this is ok
$scope.grades.push({some-new-data-here});
//Deleting old like this is ok
$scope.grades.splice(row.rowIndex, 1);
But this approach does auto-generated int-based keys to the firebase that change all the time when modified so I wanted to get control of the keys so I changed adding the items to use instead:
var fbRef = new Firebase($scope.fireBaseUrl);
var newRef = fbRef.push({some-items-here});
And everything works ok storing the data into firebase with my non-integer ids, but it doesn't bind to the ng-grid anymore. Dumping the 'grades' into the console shows all the rows being returned, but it doesn't show up in the ng-grid.
So in a nutshell: Integer based index generated by angularFire works ok in ng-grid, custom alphanumeric created by firebase.push doesn't.
I hope it doesn't sound too cryptic. Thought of doing a fiddle, but let's hope it's some 'gotcha' I've overlooked and simple to solve. If not I'll try to patch one up.
Thanks!
As per the documentation, $firebase always returns objects, not arrays, which don't work so well with ng-grid. In this case, angularfire provides a filter to help, orderByPriority. Make sure to inject $filter into your controller.
(Note: I did not use promises or watchers, only the built in events with angularfire)
$scope.grades = $firebase(new Firebase($scope.fireBaseUrl));
$scope.gradesData=[];
$scope.gridOptions = { data: 'gradesData'}
$scope.grades.$on("loaded", function(data) {
var arrData = $filter("orderByPriority")(data);
$scope.$apply(function(){$scope.gradesData = arrData;});
});
You can try a similar approach for $on.('change'...) for new rows.
hope that helps

Linking MVC In AngularJS

I have a basic application in AngularJS. The model contains a number of items and associated tags of those items. What I'm trying to achieve is the ability to filter the items displayed so that only those with one or more active tags are displayed, however I'm not having a lot of luck with figuring out how to manipulate the model from the view.
The JS is available at http://jsfiddle.net/Qxbka/2 . This contains the state I have managed to reach so far, but I have two problems. First off, the directive attempts to call a method toggleTag() in the controller:
template: "<button class='btn' ng-repeat='datum in data' ng-click='toggleTag(datum.id)'>{{datum.name}}</button>"
but the method is not called. Second, I'm not sure how to alter the output section's ng-repeat so that it only shows items with one or more active tags.
Any pointers on what I'm doing wrong and how to get this working would be much appreciated.
Update
I updated the method in the directive to pass the data items directly, i.e.
template: "<button class='btn' ng-repeat='datum in data' ng-click='toggle(data, datum.id)'>{{datum.name}}</button>"
and also created a toggle() method in the directive. By doing this I can manipulate data and it is reflected in the state HTML, however I would appreciate any feedback as to if this is the correct way to do this (it doesn't feel quite right to me).
Still stuck on how to re-evaluate the output when a tag's value is updated.
You can use a filter (docs) on the ng-repeat:
<li ng-repeat="item in items | filter:tagfilter">...</li>
The argument to the filter expression can be many things, including a function on the scope that will get called once for each element in the array. If it returns true, the element will show up, if it returns false, it won't.
One way you could do this is to set up a selectedTags array on your scope, which you populate by watching the tags array:
$scope.$watch('tags', function() {
$scope.selectedTags = $scope.tags.reduce(function(selected, tag) {
if (tag._active) selected.push(tag.name);
return selected;
}, []);
}, true);
The extra true in there at the end makes angular compare the elements by equality vs reference (which we want, because we need it to watch the _active attribute on each tag.
Next you can set up a filter function:
$scope.tagfilter = function(item) {
// If no tags are selected, show all the items.
if ($scope.selectedTags.length === 0) return true;
return intersects($scope.selectedTags, item.tags);
}
With a quick and dirty helper function intersects that returns the intersection of two arrays:
function intersects(a, b) {
var i = 0, len = a.length, inboth = [];
for (i; i < len; i++) {
if (b.indexOf(a[i]) !== -1) inboth.push(a[i]);
}
return inboth.length > 0;
}
I forked your fiddle here to show this in action.
One small issue with the way you've gone about this is items have an array of tag "names" and not ids. So this example just works with arrays of tag names (I had to edit some of the initial data to make it consistent).

Resources