Angular UI grid - Translate grid on fly - angularjs

I am using angular ui-grid. I want to translate the grid on fly. For example my current language is English. The grid gets rendered in English. Now I switch to french. I want all my menu options to be translated to french. How can I achieve this? This is my link to plunkr.
http://plnkr.co/edit/tpdNYirUEIF3RL0kf2d7?p=preview
Here is my sample code
HTML
<!doctype html>
<html ng-app="app">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.js"></script>
<script src="http://ui-grid.info/docs/grunt-scripts/csv.js"></script>
<script src="http://ui-grid.info/docs/grunt-scripts/pdfmake.js"></script>
<script src="http://ui-grid.info/docs/grunt-scripts/vfs_fonts.js"></script>
<script src="http://ui-grid.info/release/ui-grid-unstable.js"></script>
<script src="https://rawgithub.com/PascalPrecht/bower-angular-translate/master/angular-translate.min.js"></script>
<script src="app.js"></script>
<link rel="stylesheet" href="http://ui-grid.info/release/ui-grid-unstable.css" type="text/css">
<link rel="stylesheet" href="main.css" type="text/css">
</head>
<body>
<div ng-controller="MainCtrl">
<select ng-model="lang" ng-options="l for l in langs"></select><br>
<div ui-i18n="{{lang}}">
<p>Using attribute:</p>
<p ui-t="groupPanel.description"></p>
<br/>
<p>Using Filter:</p>
<p>{{"groupPanel.description" | t}}</p>
<p>Click the header menu to see language. NOTE: TODO: header text does not change after grid is rendered. </p>
<div ui-grid="gridOptions" class="grid"></div>
</div>
</div>
</body>
</html>
My JS
var app = angular.module('app', ['ngTouch', 'ui.grid', 'pascalprecht.translate']);
app.controller('MainCtrl', ['$scope', 'i18nService', '$http', '$translate','$rootScope', function ($scope, i18nService, $http, $translate,$rootScope) {
$scope.langs = i18nService.getAllLangs();
$scope.lang = 'en'
$scope.gridOptions = {
columnDefs: [
{ displayName: 'NAME', field: 'name', headerCellFilter: 'translate' },
{ displayName: 'GENDER', field: 'gender', headerCellFilter: 'translate' },
{ displayName: 'COMPANY', field: 'company', headerCellFilter: 'translate', enableFiltering: false }
]
};
$http.get('https://cdn.rawgit.com/angular-ui/ui-grid.info/gh-pages/data/100.json')
.success(function(data) {
$scope.gridOptions.data = data;
});
}]);
app.config(function ($translateProvider) {
$translateProvider.translations('en', {
GENDER: 'Gender',
NAME: 'Name',
COMPANY: 'Company'
});
$translateProvider.translations('de', {
GENDER: 'Geschlecht',
NAME: 'Name',
COMPANY: 'Unternehmen'
});
$translateProvider.preferredLanguage('en');
});
The first screenshot refers to default language English. When I change my language to 'de' the grid menu options don't get translated. How can I make this happen?

To translate the Grid on the fly if you are using "Angular Translate", you should only refresh the Grid language when the angular-translate's event "$translateChangeSuccess" get fired like below:
// Get Fired when you change language using angular-translate
$rootScope.$on('$translateChangeSuccess', function (event, a) {
$scope.language = $translate.proposedLanguage() || $translate.use();
i18nService.setCurrentLang($scope.language); // Refresh the grid language
});
Do not forget to inject $rootScope and i18nService.

I needed to translate on the fly (without page refresh) those custom menu items too as well as "items per page" and such in the pager.
I also wrote an hack/workaround directly in the ui-grid source code so sharing if it might help someone else, at least until there will be an official patch.
first at grid definition a new event to handle language changed on
the fly (for example via angular dynamic locale):
onRegisterApi: function(gridApi) {
gridApi.registerEvent('language', 'changed');
gridApi.language.on.changed($scope, function(language) {
$rootScope.$gridLanguage = language;
});
then in a controller after language changed raise that event (in my
case on $localeChangeSuccess from angular dynamic locale) :
$scope.$on('$localeChangeSuccess', function (e, locale) {
$scope.$View.GridApi.language.raise.changed(locale);
});
and here the hacks, where the texts need a refresh, for example
adding in uiGridColumnMenu directive link function:
$scope.$watch('$parent.$root.$gridLanguage', function () {
if ($scope.$parent.$root.$gridLanguage !== undefined) {
$scope.menuItems = uiGridColumnMenuService.getDefaultMenuItems($scope);
}
});
or the same for uiGridPager:
$scope.$watch('$parent.$root.$gridLanguage', function () {
if ($scope.$parent.$root.$gridLanguage !== undefined) {
$scope.sizesLabel = i18nService.getSafeText('pagination.sizes');
$scope.totalItemsLabel = i18nService.getSafeText('pagination.totalItems');
$scope.paginationOf = i18nService.getSafeText('pagination.of');
$scope.paginationThrough = i18nService.getSafeText('pagination.through');
}
});

Working Plnkr
Add following method in controller:
$scope.changeLanguage = function (key) {
$translate.use(key);
};
Call this method with ng-change:
<select ng-model="lang" ng-options="l for l in langs" ng-change="changeLanguage(lang)"></select>

Related

Return array of objects from Handlebars Helper

I'm trying to write a helper that will return an array of objects that can then be looped through. Here's what I have now:
Handlebars.registerHelper('testHelper', () => {
return [
{ slug: 'Test', title: 'This is it!' },
{ slug: 'Test 2', title: 'This is the second it!' },
];
});
Using it like:
{{#entries this}}
{{title}}
{{/entries}}
And I'm receiving [object, Object] for each object in the array instead of the individual values. Please help :)
Thanks!
The way helpers in Handlebars work is a bit tricky. Instead of passing data from the helper to the main template body, you pass the portion of the template body related to the helper to the helper.
So, for example, when you do this:
{{#entries this}}
{{title}}
{{/entries}}
You are providing two things to the entries helper:
1) the current context (this)
2) some template logic to apply
Here's how the helper gets these items:
Handlebars.registerHelper('entries', (data, options) => {
// data is whatever was provided as a parameter from caller
// options is an object provided by handlebars that includes a function 'fn'
// that we can invoke to apply the template enclosed between
// #entries and /entries from the main template
:
:
});
So, to do what you want to do:
Handlebars.registerHelper('testHelper', (ignore, opt) => {
var data = [
{ slug: 'Test', title: 'This is it!' },
{ slug: 'Test 2', title: 'This is the second it!' },
];
var results = '';
data.forEach( (item) => {
results += opt.fn(item);
});
return results;
});
The opt.fn(item) applies this portion of template:
{{title}}
and the idea is to create a string (a portion of your html) that is then returned and placed into the string being formulated by your main template.
Here's a sample to show this working.
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/2.0.0/handlebars.js"></script>
</head>
<body>
<script id="t" type="text/x-handlebars">
{{#testHelper this}}
{{title}}
{{/testHelper}}
</script>
<script>
Handlebars.registerHelper('testHelper', (ignore, opt) => {
var data = [
{ slug: 'Test', title: 'This is it!' },
{ slug: 'Test 2', title: 'This is the second it!' },
];
var results = '';
data.forEach((item) => {
results += opt.fn(item);
});
return results;
});
var t = Handlebars.compile($('#t').html());
$('body').append(t({}));
</script>
</body>
</html>
Let me also echo what others have been trying to tell you. It doesn't make a lot of sense to try to populate data within your templates. This should be passed as context for your templates to act on. Otherwise, you are mixing your business logic with your template logic (view) and this complicates things needlessly.
Here's a simple change you can make in the same snippet, passing the data to your templates:
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/2.0.0/handlebars.js"></script>
</head>
<body>
<script id="t" type="text/x-handlebars">
{{#testHelper this}}
{{title}}
{{/testHelper}}
</script>
<script>
Handlebars.registerHelper('testHelper', (ignore, opt) => {
var results = '';
data.forEach((item) => {
results += opt.fn(item);
});
return results;
});
var data = [
{ slug: 'Test', title: 'This is it!' },
{ slug: 'Test 2', title: 'This is the second it!' },
];
var t = Handlebars.compile($('#t').html());
$('body').append(t(data));
</script>
</body>
</html>
This way you can retrieve your data in your javascript and keep the templates for what they were intended - formulating html.

Backbone Marionettejs view does not change when model changed

I follow the example from this book https://leanpub.com/marionette-gentle-introduction. My problem is that the view does not rerender when i change the model by clicking on the button. As the answer from this question , i don't need to do anything because Backbone/MarionetteJS smart enough to change the view.
Here is the code
<!DOCTYPE html>
<html lang="en">
<head>
<title>Demo marionettejs</title>
<script src="./vendors/jquery/dist/jquery.js" type="text/javascript"></script>
<script src="./vendors/underscore/underscore.js" type="text/javascript"></script>
<script src="./vendors/backbone/backbone.js" type="text/javascript"></script>
<script src="./vendors/backbone.marionette/lib/backbone.marionette.js" type="text/javascript"></script>
</head>
<body>
<div id="main-region" class="container">
<p>Here is static content in the web page. You'll notice that it gets
replaced by our app as soon as we start it.</p>
</div>
<script type="text/template" id="contact-template">
<p><%- firstName %> <%- lastName %> : <%- time %> </p> <br />
<button>Change model</button>
</script>
<script type="text/javascript">
var ContactManager = new Marionette.Application();
ContactManager.Contact = Backbone.Model.extend({});
ContactManager.ContactView = Marionette.ItemView.extend({
template: "#contact-template",
initialize: function () {
this.currentMeterId = null;
},
events: {
"click button": "changeModel"
},
modelEvents: {
"change": "modelChanged"
},
changeModel: function() {
this.model.set("time", (new Date()).toString());
},
modelChanged: function() {
console.log("Model changed : " + this.model.get('time'));
},
//EDIT
onRender: function() {
//Create jsTree here.
}
});
ContactManager.on("before:start", function () {
var RegionContainer = Marionette.LayoutView.extend({
el: "#app-container",
regions: {
main: "#main-region"
}
});
ContactManager.regions = new RegionContainer();
});
ContactManager.on("start", function () {
var alice = new ContactManager.Contact({
firstName: "Alice",
lastName: "Arten",
time: "#"
});
var aliceView = new ContactManager.ContactView({
model: alice
});
ContactManager.regions.main.show(aliceView);
});
ContactManager.start();
</script>
</body>
</html>
#Edit
This code is just sample. In my real app, I have an ajax task that changes DOMs in the view. This ajax task creates a tree (jsTree) in onRender event. If i use modelEvents: {"change": "render"}, my jsTree will be reload and lost its state. So I want only update the model values in the view, others DOMs is retain.
The accepted answer to the question you pointed points to another question which has the following:
modelEvents: {
'change': "modelChanged"
},
modelChanged: function() {
console.log(this.model);
this.render();
}
And the most upvoted answer suggests the same:
modelEvents: {
'change': 'fieldsChanged'
},
fieldsChanged: function() {
this.render();
}
a comment to the most upvoted answer suggests
just {'change': 'render'} does the trick too
Which means you can do
modelEvents: {
'change': 'render'
}
So somehow you need to tell marionette invoke render on model changes.
I don't think backbone and marionette couple is smart enough to know whether you need to render view on model changes or you don't want to unless you tell them ;)

ng-click error with angular 1.2.0 and 1.2.1

I have some angular code that worked in angularjs 1.2.0-rc.1, rc.2 and rc.3. But it doesn't work in 1.2.0 and 1.2.1.
I have illustrated the problem on http://plnkr.co/edit/KBYFJQ2sZeOJ79Hid1gG
My HTML is
<!DOCTYPE html>
<html ng-app="app">
<head>
<script data-require="angular.js#1.2.1" data-semver="1.2.1"
src="http://code.angularjs.org/1.2.1/angular.js"></script>
<link rel="stylesheet" href="style.css"/>
<script src="script.js"></script>
</head>
<body ng-controller="AppCtrl">
<ul>
<li ng-repeat="menuEntry in menuItems">
<a ng-href="#" ng-click="{{menuEntry.action}}">{{menuEntry.text}}</a>
</li>
</ul>
</body>
</html>
and my angular code is
'use strict';
angular.module('app', [])
.controller('AppCtrl', function ($scope) {
var menuItems = [
{
text: "Log off",
action: 'logoff()'
}
];
var logoff = function () {
alert("logoff called")
};
$scope.menuItems = menuItems;
$scope.logoff = logoff;
});
If I run this code with 1.2.0-rc.3 it runs without error and I get an alert box when I click on the link. But if I run it with 1.2.0 or 1.2.1 I get an error:
Error: [$parse:syntax] Syntax Error: Token 'menuEntry.action' is unexpected, expecting [:] at column 3 of the expression [{{menuEntry.action}}] starting at [menuEntry.action}}].
Can anybody help me with this problem?
The docs for ngClick don't indicate that you can use {{..}} bindings for the callable expression. I think it may just work by accident in older versions, and an implementation change in the newer versions has broken it.
Really this seems a strange way to be hooking your code up anyway. Are you able to replace the action attribute with a real function to call? e.g.
angular.module('app', [])
.controller('AppCtrl', function ($scope) {
$scope.logoff = function () {
alert("logoff called")
};
$scope.menuItems = [
{
text: "Log off",
action: $scope.logoff
}
];
});
Then
<a ng-href="#" ng-click="menuEntry.action()">{{menuEntry.text}}</a>
If you do need to start with a dynamic expression you can call it with $scope.eval. e.g.
var menuItems = [
{
text: "Log off",
action: 'logoff()'
}
];
angular.forEach(menuItems, function(menuItem) {
menuItem.callableAction = function() { return $scope.$eval(menuItem.action); }
});
then
<a ng-href="#" ng-click="menuEntry.callableAction()">{{menuEntry.text}}</a>

Why doesn't this.$el.append() work?

I'm trying to follow along http://addyosmani.github.io/backbone-fundamentals. I'm not getting how $el is supposed to work in a view.
Here's my HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Dashboard</title>
</head>
<body>
<h1>Dashboard</h1>
<ol class="foo" id="recent-station">
</ol>
<!-- Templates -->
<script type="text/template" id="station-template">
<li><%= station %></li>
</script>
<!-- Javascript -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone-min.js"></script>
<script src="static/js/script.js"></script>
</body>
</html>
And script.js is:
var RecentStation = Backbone.Model.extend( {
defaults: {
station: "",
},
initialize: function() {
console.log('initialized: ' + JSON.stringify(this));
this.on('change', function() {
console.log('changed: ' + JSON.stringify(this));
})
}
});
var RecentStationView = Backbone.View.extend( {
tagName: 'ol',
id: 'recent-station',
initialize: function() {
this.model.bind('change', _.bind(this.render, this));
},
render: function() {
console.log('render');
this.$el.append('<li>foo</li>');
$('ol#recent-station').append('<li>bar</li>');
return this;
},
});
var recent = new RecentStation();
var recentView = new RecentStationView({model: recent});
recent.set('station', 'My Station');
The interesting stuff is happening in the render function. I can see "render" logged to the console, and the "bar" text gets appended to the node, but not the "foo" text. I thought this.$el and $('ol#recent-station') were the same thing, but obviously not. What am I missing?
If you don't specify a dom element using el attribute, one will be created using tagName,id,className, and attributes from the view.
In your case you don't specify an el attribute in your view so you create an element that looks like:
<ol id='recent-station'></ol>
You then append <li>foo</li> into it, but your view element is still not in the DOM.
$('ol#recent-station') returns the dom element included in your html which is different than your view element, but has the same attributes.
So, in your example you would need to specify an existing element by supplying an el attribute.
var RecentStationView = Backbone.View.extend( {
// remove tagName and id
el:'#recent-station',
/* rest of your code below */
A fiddle with the changes, http://jsfiddle.net/DsRJH/.

How to refresh ng-grid when griddata has different number of columns from previous displayed griddata

I do have an application where user selects an option to get grid data from server side and depend upon what option user selects, the number of columns in grid data may vary. I am using Angularjs and ng-grid to display data. The first time, it works fine and it does display correct number of columns (e.g 2 columns). but when user selects other option, which gets 3 columns grid-data, ng-grid still thinks of old columns information and tries to map second 3-cols grid-data in old 2 columns. Since columns info is not available, I cannot use columnDefs in controller. How can I refresh ng-grid columns.
Note: I tried to put code at jsFiddle (http://jsfiddle.net/User888/pwUeX/11/), but somehow, I am not able to run it. My complete code is here.
Also: How can I display row# in ng-grid without sending it from server.
My HTML file:
<!DOCTYPE html>
<html ng-app="myApp">
<head lang="en">
<meta charset="utf-8">
<title>test </title>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.min.js"></script>
<script type="text/javascript" src="http://angular-ui.github.io/ng-grid/lib/ng-grid.js"></script>
<script type="text/javascript" src="gridExample.js"></script>
<link rel="stylesheet" type="text/css" href="http://angular-ui.github.io/ng-grid/css/ng-grid.css" />
<link rel="stylesheet" type="text/css" href="css/style.css" />
</head>
<body ng-controller="GridExampleCtrl">
<div>
Grid Selection:
<select ng-model="gridSelectedId">
<option ng-repeat="gridSel in gridSels" value="{{gridSel.GridSelId}}">{{gridSel.GridSelName}}</option>
</select>
<br/>User selected: {{gridSelectedId}} <br><hr>
<div>
<button ng-click="display()">Display</button><hr>
<div class="gridStyle" ng-grid="gridOptions"></div>
</div>
</div>
</body>
</html>
My Controller is:
var app = angular.module('myApp', ['ngGrid']);
app.controller('GridExampleCtrl', function ($scope, $http, $timeout) {
$scope.myData = [];
$scope.grid1 = [{name: "grid1a", age: 50},
{name: "grid1b", age: 43},
{name: "grid1c", age: 50},
{name: "grid1d", age: 29},
{name: "grid1e", age: 34}];
$scope.grid2 = [{lastname: "grid2a", age: 50, state:'Idaho'},
{lastname: "grid2b", age: 43, state:'NewYork'},
{lastname: "grid2c", age: 50, state:'California'},
{lastname: "grid2d", age: 29, state:'Arizona'},
{lastname: "grid2e", age: 34, state:'Utah'}];
$scope.gridSels = [
{GridSelId : 1, GridSelName : 'Grid 1' },
{GridSelId : 2, GridSelName : 'Grid 2' }
]
$scope.gridOptions = {
data: 'myData',
enableColumnResize: true,
showGroupPanel: true,
//pagingOptions: $scope.pagingOptions
};
$scope.display = function(){
console.log("User selected grid : " + $scope.gridSelectedId);
if ($scope.gridSelectedId == 1) {
$scope.myData = $scope.grid1;
} else {
$scope.myData = $scope.grid2;
}
};
});
and css looks like:
.gridStyle {
border: 1px solid rgb(212,212,212);
width: 800px;
height: 400px;
}
You could also call a method that is supplied in the source code. This may not have been available at the time of the question but I ran into the issue myself. If you're able to target the grid itself you can use something similar to this piece of code
scope.gridOptions.ngGrid.buildColumns();
buildColumns() is the major function and you need to redefine the columnDefs array as brentiumbrent mentioned
Though a bug in ng-grid prevents this answer from working well with enableColumnResize: true (changes to columnDefs after a column change will result in invisible data), under basic circumstances you can do something along the lines of
$scope.colDefs = [];
$scope.$watch('myData', function() {
$scope.colDefs = [];
angular.forEach(Object.keys($scope.myData[0]), function(key){
$scope.colDefs.push({ field: key });
});
}
);
$scope.gridOptions = {
data: 'myData',
columnDefs: 'colDefs',
enableColumnResize: false,
showGroupPanel: true
};
Here's a working plunker demonstrating it with your example:
http://plnkr.co/edit/w1DLtS
I also ran into this issue when I tried to make this as service(don't have to setup it by myself all the time I need it).
A piece of my service:
setColumns: function(columnDefs){
$rootScope.columnDefs = columnDefs;
return $rootScope.columnDefs;
}
I give new column names and fields in array (columnDefs) after I assign new columns.

Resources