I have a column in an ng-grid table that contains a certain field. I need to give it 2 different names based on the currently logged in user. How can I do that?
Constraints that make it less that straightforward. The name is loaded via an XHR as appConfig.columnName (stored on $rootScope).
columnDefs:
{
displayName: "{{ appConfig.columnLabel }}",
never interpolates, just displays as {{ appConfig.columnLabel }}
columnDefs:
{
displayName: $rootScope.appConfig.columnLabel,
fail sometimes because appConfig might not be defined yet. (This does work if I load a different view first)
I'm not sure how to wait on appConfig though, it is loaded as a service independent of which view is loading. And even if I do, how do I defer setting the config on the ng-grid?
Seems like this should be really easy, but is proving difficult.
Looking # this issue https://github.com/angular-ui/ng-grid/issues/128 I figured it out. I move the columnDefs to the $scope like so:
$scope.gridColumnDefs = [
...
{
displayName: $rootScope.appConfig && $rootScope.appConfig.columnLabel ? $rootScope.appConfig.columnLabel : "",
field: 'something',
}
...
]
Let the grid config watch the column defs like so:
$scope.gridOptions = {
...
columnDefs: 'gridColumnDefs',
...
};
Then watch the appConfig object on the $rootScope to await the arrival of the appConfig data and adjust the columnDefs with the title.
if(!$rootScope.appConfig.hasOwnProperty('columnLabel')) {
var unwatch = $rootScope.$watch('appConfig',function(newValue, oldValue) {
if(newValue.employerLabel) {
_.forEach($scope.gridColumnDefs, function(colDef) {
if(obj.field == 'something') {
colDef.displayName = $rootScope.appConfig.columnLabel;
}
});
unwatch();
unwatch = angular.noop; // $destroy listener is going to call unwatch
}
});
$scope.$on("$destroy", function() { // Don't leak this watcher on the $rootScope
unwatch();
});
}
Related
Currently I'm getting a pretty messy response from the server, and If store everything in one Object and then bind that as model on formly-form I have huge lag on when typing something to text input.It's caused because there are 3 arrays with over 850 items and as I understand Angular Formly observe those things too.
So I decided to split data in 2 objects
One that holds everything and It's not binded as model to formly-form
Other that holds initial data which are pretty light, and those things are binded to the formly-form model.
Here is the skeleton:
$scope.allData = {}
$scope.formlyData = {}
$scope.initData = function() {
myService.get()
.then(function(response) {
$scope.allData = response.data
$scope.formlyData['important'] = response.data.important
})
}
Remember huge arrays ? Well basically they should be stored in select inputs, and with formly I tried to make It like this:
var vm = $scope
$scope.formFields = [{
fieldGroup: [
{
'key': 'actors',
'type': 'select',
'className': 'col-sm-6',
'templateOptions': {
options: []
},
controller: function($scope) {
$scope.to.options = vm.allData.actors
}
}
]
}]
And this doesn't work because allData is empty when It's executed and It's populated later because of $http call which is async.
Okay I tried to use watcher:
var vm = $scope;
$scope.formFields = [
fieldGroup: [
{
'key': 'actors',
'type': 'select',
'className': 'col-sm-6',
'templateOptions': {
options: []
},
controller: function($scope) {
vm.$watch('allData', function(newVal) {
$scope.to.options = newVal.actors
})
}
}
]
]
But something is not good and loading takes a quite long (response is fetched for 200ms).Is It because I have about 10 fields with same behaviour and then there is a many watchers on same object ?
Any better idea to handle this, or sort of improvment ? Thanks.
I am creating an ionic project and I am trying to integrate with Algolia autocomplete.js. I managed to make the search system work, however I added a ng-click on my search results and this function is not working as presented in this codepen that I did as example below:
http://codepen.io/marcos_arata/pen/VKVOky
Inside my algolia's result template:
<a ng-click="add_name({{{ name }}})">
Function that should be run when clicked:
$scope.add_name = function(name) {
alert('User added!');
console.log(name);
}
I tried to inject the results inside the scope but didn't work as well:
autocomplete('#search_name', { hint: false, debug: true, openOnFocus: true },[{
source: index.ttAdapter({ hitsPerPage: 15 }),
templates: {
header: '',
suggestion: function(hit) {
$scope.hit = hit;
return template.render(hit);
}
}
}]);
http://codepen.io/marcos_arata/pen/VKVOky
---- SOLVED ----
Instead of creating a ng-click function inside your templates, you can handle the event click of your search inside your "autocomplete:selected" function and use the dataset and suggestion results.
.on('autocomplete:selected', function(event, suggestion, dataset) {
$scope.name = suggestion.name;
console.log($scope.name);
## create any functions with the suggestion and dataset results inside
});
EDITING THE ANSWER:
Here is the codepen:
Apparently the suggestion keep the name clicked, so you dont need an extra function:
.on('autocomplete:selected', function(event, suggestion, dataset) {
$scope.name = suggestion.name;
console.log($scope.name);
});
I have this html:
<div ng-app='myApp'>
<div ng-controller='testCtrl'>
<h3>All possesions</h3>
{{possessions}}
<h3>Green cars</h3>
{{greenishCars}}
</div>
</div>
And this script:
angular.module('myApp', [])
.factory('getPossessionsService', ['$timeout',
function($timeout) {
var possessions = {};
$timeout(function() {
possessions.cars = [{
model: "Mazda 6",
color: "lime green"
}, {
model: "Audi A3",
color: "red"
}, {
model: "Audi TT",
color: "green"
}, {
model: "Volkswagen Lupo",
color: "forest green"
}];
possessions.jewelry = [{
type: "ring",
metal: "gold"
}, {
type: "earring",
metal: "silver"
}];
}, 1000);
return possessions;
}
])
.controller('testCtrl', ['$scope', 'filterFilter', 'getPossessionsService',
function($scope, filterFilter, getPossessionsService) {
$scope.possessions = getPossessionsService;
$scope.greenishCars = filterFilter($scope.possessions.cars, carColorIsGreenShade);
function carColorIsGreenShade(car) {
return ['green', 'forest green', 'lime green'].indexOf(car.color) != -1;
}
}
]);
I am trying to get $scope.greenishCars to update correctly when the data is available, but it is not. I understand that this has because $scope.possessions.cars is an array and therefore not a reference to the data, so it is not updated. But how should I alter my script so that greenishCars get updated when the data arrives? I am guessing I should use $scope.possessions "directly", but I do not quite see how I should rewrite this nicely....
See this plunk.
Edit: Thoughts on which answer to choose
As in comments in the answers to Oliver and MajoB, I ended up with using both filter and watch. In my special case the request for the data was made in another place (not in the controller of my page in question), so it was not so easy to act on the resolving of the promise (with promise.then as suggested by Oliver), I therefore used a watch. But there is a couple of things to be aware of with watches. If the variable you want to watch is not on the scope, then you must provide the variable by returning it from a function. And if you want to watch for a change in an existing property (say somebody repaints my existing Mazda 6 in a different color), then none of the watch-answers works, as you need to add "true" when calling $watch. When you add 'true' as the second paramenter to $watch, then it watches for changes in the actual values of the variable, and it also does this check for values deeply in an object/array (without it just checks references, see this blog). I ended up with this controller/$watch for my real-life use-case (which is a little bit different that in my example above, and is based on Olivers filter-plunk), and it looks like this (changed to fit the plunk):
.controller('testCtrl', ['$scope', 'greenFilter', 'getPossessionsService',
function($scope, greenFilter, getPossessionsService) {
var none-scope-possessions = getPossessionsService; // I do not want to expose all properties on the scope....
$scope.greenishCars = [];
$scope.$watch(function() {return none-scope-possessions.cars}, function(newValue){
if (newValue) {
$scope.greenishCars = greenFilter(newValue);
}
},true);
}
]);
You can watch the data changes:
$scope.$watch('possessions.cars', function(){
$scope.greenishCars = filterFilter($scope.possessions.cars, carColorIsGreenShade);
});
I would recommend just building your own custom filter like so:
.filter('green', function() {
return function(possessions) {
if (!possessions) return null;
var filtered = [];
angular.forEach(possessions, function(possesion){
if (['green', 'forest green', 'lime green'].indexOf(possesion.color) != -1) {
filtered.push(possesion);
}
});
return filtered;
};
})
Then pass your collection through that filter like so:
<div ng-controller='testCtrl'>
<h3>All possesions</h3>
{{possessions}}
<h3>Green cars</h3>
{{possessions.cars|green}}
</div>
Everything else will happen automatically. See the updated plunkr.
You can watch possesions collection for changes:
$scope.$watchCollection('possessions', function (newValue) {
if (newValue)
{
$scope.greenishCars = filterFilter(newValue.cars, carColorIsGreenShade);
}
});
http://plnkr.co/edit/Sl6dW0pqKr593OXrgDb9?p=preview
First thing is , var possessions = {}; you have declared an object , cars is an field in this object , which has array of cars . so either return possessions.cars or in the markup call it via possessions.cars[0].. if you want to call only one value ..or if you want to display all the values the use ng-repeat=" car in possessions.cars" .
I'm new to extjs and I'm using the MVC architecture.
When my application references a method of a controller, I do it that way (in MyApp.Application):
Mb.app.getController('Main').myMethod();
It is already long, but I think this is the way to do.
When a controller calls it's own method in a closure, I was led to use this code (in MyApp.controller.Main:
controllerMethodOne: function(){
Ext.Ajax.request({
url: ...,
params: ...,
success: (function(response){
list = Ext.JSON.decode(response.responseText);
list.forEach(function(item){
storeMenu.add(
Ext.create('Ext.menu.Item', {
text: item.text,
handler: function(el){MyApp.app.getController('Main').controllerMethodTwo()}
})
)
})
})
})
},
I referenced the method with MyApp.app.getController('Main').controllerMethodTwo() because this is not refering to the controller object in the closure, and thus this..controllerMethodTwo()isn't working.
I find this utterly convoluted, and I hope someone has an idea to get around that MyApp.app.getController-workaround.
Update
Thanks to all the suggestion I could optimize my code and came up with:
// in my controller
mixins: ['Mb.controller.mixin.StoreMenu'],
// I use that style of menus in two controllers thats why I use a mixin
init: function() {
this.control({
'#vg_storeMenu menuitem': {
click: this.onStoreMenuClicked
}
})
},
// the controller mixin
Ext.define('Mb.controller.mixin.StoreMenu', {
extend: 'Ext.app.Controller',
buildStoreMenu: function(store_name){
var storeMenu = Ext.ComponentQuery.query('#' + store_name + 'Menu')[0];
Ext.Ajax.request({
url: Paths.ajax + 'json.php',
params: {list: store_name + 's'},
success: (function(response){
list = Ext.JSON.decode(response.responseText);
items = Ext.Array.map(list, function(item) {
return {
xtype: 'menuitem',
text: item.text
}
});
storeMenu.add(items);
})
})
},
onStoreMenuClicked: function(el){
...
}
});
Actually, there are at least four distinctly different problems in your code:
Scope handling for intra-class method calls
Component creation inefficiency
Component event handling in a controller
Inter-controller communication
Scope handling
The first one is solved either by using a closure, or passing in the scope parameter to Ajax request, as #kevhender described above. Given that, I'd advocate writing clearer code:
controllerMethodOne: function() {
Ext.Ajax.request({
url: ...,
params: ...,
scope: this,
success: this.onMethodOneSuccess,
failure: this.onMethodOneFailure
});
},
// `this` scope is the controller here
onMethodOneSuccess: function(response) {
...
},
// Same scope here, the controller itself
onMethodOneFailure: function(response) {
...
}
Component creation
The way you create menu items is less than efficient, because every menu item will be created and rendered to the DOM one by one. This is hardly necessary, either: you have the list of items upfront and you're in control, so let's keep the code nice and declarative, as well as create all the menu items in one go:
// I'd advocate being a bit defensive here and not trust the input
// Also, I don't see the `list` var declaration in your code,
// do you really want to make it a global?
var list, items;
list = Ext.JSON.decode(response.responseText);
items = Ext.Array.map(list, function(item) {
return {
xtype: 'menuitem',
text: item.text
}
});
// Another global? Take a look at the refs section in Controllers doc
storeMenu.add(items);
What changes here is that we're iterating over the list and creating a new array of the soon-to-be menu item declarations. Then we add them all in one go, saving a lot of resources on re-rendering and re-laying out your storeMenu.
Component even handling
It is completely unnecessary, as well as inefficient, to set a handler function on every menu item, when all this function does is call the controller. When a menu item is clicked, it fires a click event - all you need to do is to wire up your controller to listen to these events:
// Suppose that your storeMenu was created like this
storeMenu = new Ext.menu.Menu({
itemId: 'storeMenu',
...
});
// Controller's init() method will provide the wiring
Ext.define('MyController', {
extend: 'Ext.app.Controller',
init: function() {
this.control({
// This ComponentQuery selector will match menu items
// that descend (belong) to a component with itemId 'storeMenu'
'#storeMenu menuitem': {
click: this.controllerMethodTwo
}
});
},
// The scope is automatically set to the controller itself
controllerMethodTwo: function(item) {
...
}
});
One best practice is to write the ComponentQuery selectors as finely grained as feasible, because they're global and if you're not precise enough your controller method may catch events from unwanted components.
Inter-controller communication
This is probably a bit far fetched at the moment, but since you're using Ext JS 4.2 you may as well take advantage of the improvements we've added in that regard. Before 4.2, there was a preferred (and only) approach to call one controller's methods from another controller:
Ext.define('My.controller.Foo', {
extend: 'Ext.app.Controller',
methodFoo: function() {
// Need to call controller Bar here, what do we do?
this.getController('Bar').methodBar();
}
});
Ext.define('My.controller.Bar', {
extend: 'Ext.app.Controller',
methodBar: function() {
// This method is called directly by Foo
}
});
In Ext JS 4.2, we've added the concept of event domains. What it means is that now controllers can listen not only to component's events but to other entities events, too. Including their own controller domain:
Ext.define('My.controller.Foo', {
extend: 'Ext.app.Controller',
methodFoo: function() {
// Effectively the same thing as above,
// but no direct method calling now
this.fireEvent('controllerBarMethodBar');
}
});
Ext.define('My.controller.Bar', {
extend: 'Ext.app.Controller',
// Need some wiring
init: function() {
this.listen({
controller: {
'*': {
controllerBarMethodBar: this.methodBar
}
}
});
},
methodBar: function() {
// This method is called *indirectly*
}
});
This may look like a more convoluted way to do things, but in fact it's a lot simpler to use in large(ish) apps, and it solves the main problem we've had: there is no need for hard binding between controllers anymore, and you can test each and every controller in isolation from others.
See more in my blog post: Controller events in Ext JS 4.2
this doesn't work in the success callback because it doesn't have the right scope. Your 2 options are to:
1: Create a variable at the beginning of the function to reference in the callback:
controllerMethodOne: function(){
var me = this;
Ext.Ajax.request({
url: ...,
params: ...,
success: (function(response){
list = Ext.JSON.decode(response.responseText);
list.forEach(function(item){
storeMenu.add(
Ext.create('Ext.menu.Item', {
text: item.text,
handler: function(el){me.controllerMethodTwo()}
})
)
})
})
})
},
2: Use the scope config of the Ext.Ajax.request call:
controllerMethodOne: function(){
Ext.Ajax.request({
url: ...,
params: ...,
scope: this,
success: (function(response){
list = Ext.JSON.decode(response.responseText);
list.forEach(function(item){
storeMenu.add(
Ext.create('Ext.menu.Item', {
text: item.text,
handler: function(el){me.controllerMethodTwo()}
})
)
})
})
})
},
I have very simple question about getting data from WebSql
I have DropDown i.e
<select id="selectCatagoryFood" data-role="listview" data-native-menu="true"
ng-init="foodCatagory = foodCatagories.cast[0]"
ng-options="foodCatagory as foodCatagory.text for foodCatagory in foodCatagories.cast"
ng-model="foodCatagory"
ng-change="changeFoodCatagory()">
</select>
now i want to add data init from webSQL. I already get Data from webSql but i am confuse that how to add that data into DropDown
An example or hints maybe very helpful for me.
Update 1 :: Add Controller Code
myApp.controller('foodSelection',function($scope,foodCatagories){
$scope.foodCatagories = foodCatagories;
$scope.changeFoodCatagory = function(){
alert($scope.foodCatagory.value);
}
});
Update 2 webSQL and JayData
_context.onReady({
success: showData,
error: function (error){
console.log(error);
}
});
function showData(){
var option = '';
_context.FoodGroup.forEach(function(FG)
{
option += '<option value="'+FG.FoodGroupID+'">'+FG.Description+'</option>';
}).then(function(){
console.log(option);
});
}
Update 3
var myApp = angular.module('myApp',[]);
myApp.factory('foodCatagories',function(){
var foodCatagories = {};
foodCatagories.cast = [
{
value: "000",
text: "Select Any"
}
];
return foodCatagories;
});
Update 4
One thing that i didn't mention is that I am using JayData for getting data from webSQL to my App
I will try to explain how it works:
EDIT: Live demo
html
Here is your stripped down select.
<select ng-options="item as item.text for item in foodCategories"
ng-model="foodCategory"
ng-required="true"
ng-change="changeFoodCategory()">
</select>
The directive ng-options will fill automatically the option elements in your select. It will take the foodCategories variable from the $scope of your controller and foreach item in the collection, it will use the text property as the label shown (<option>{{item.text}}</option>') and it will select the whole objectitemas the value of the selectedoption. You could also refer to a property as the value like ({{item.text}}). Then yourng-modelwould be set to theid` value of the selected option.
The directive ng-model corresponds to the variable in the $scope of your controller that will hold the value of the selected option.
The directive ng-required allows you to check if a value has been selected. If you are using a form, you can check if the field is valid formName.ngModelName.$valid. See the docs for more details on form validation.
The directive ng-change allows you to execute a function whenever the selected option changes. You may want to pass the ng-model variable to this function as a parameter or call the variable through the $scope inside the controller.
If no default value is set, angular will add an empty option which will be removed when an option is selected.
You did use the ng-init directive to select the first option, but know that you could set the ng-model variable in your controller to the default value you would like or none.
js
Here I tried to simulate your database service by returning a promise in the case that you are doing an async request. I used the $q service to create a promise and $timeout to fake a call to the database.
myApp.factory('DbFoodCategories', function($q, $timeout) {
var foodCategories = [
{ id: 1, text: "Veggies", value: 100 },
{ id: 2, text: "Fruits", value: 50 },
{ id: 3, text: "Pasta", value: 200 },
{ id: 4, text: "Cereals", value: 250 },
{ id: 5, text: "Milk", value: 150 }
];
return {
get: function() {
var deferred = $q.defer();
// Your call to the database in place of the $timeout
$timeout(function() {
var chance = Math.random() > 0.25;
if (chance) {
// if the call is successfull, return data to controller
deferred.resolve(foodCategories);
}
else {
// if the call failed, return an error message
deferred.reject("Error");
}
}, 500);
/* // your code
_context.onReady({
success: function() {
deferred.resolve(_contect.FoodGroup);
},
error: function (error){
deferred.reject("Error");
}
});
*/
// return a promise that we will send a result soon back to the controller, but not now
return deferred.promise;
},
insert: function(item) {
/* ... */
},
update: function(item) {
/* ... */
},
remove: function(item) {
/* ... */
}
};
});
In your controller you set the variables that will be used in your view. So you can call your DbFoodCategories service to load the data into $scope.foodCategories, and set a default value in $scope.foodCategory that will be used to set the selected option.
myApp.controller('FoodSelection',function($scope, DbFoodCategories){
DbFoodCategories.get().then(
// the callback if the request was successfull
function (response) {
$scope.foodCategories = response; //response is the data we sent from the service
},
// the callback if an error occured
function (response) {
// response is the error message we set in the service
// do something like display the message
}
);
// $scope.foodCategory = defaultValue;
$scope.changeFoodCategory = function() {
alert($scope.foodCatagory.value);
}
});
I hope that this helped you understand more in detail what is happening!
See this example and how use $apply to update the data in scope.
in the new version we released a new module to support AngularJS. We've started to document how to use it, you can find the first blogpost here
With this you should be able to create your dropdown easily, no need to create the options manually. Something like this should do the trick:
myApp.controller('foodSelection',function($scope, $data) {
$scope.foodCatagories = [];
...
_context.onReady()
.then(function() {
$scope.foodCatagories = _context.FoodGroup.toLiveArray();
});
});
provided that FoodGroup has the right fields, of course