AngularJS: intro.js - change language - options in directive same language - javascript

I'm using intro.js in my angular app:
http://code.mendhak.com/angular-intro.js/example/index.html
and all was ok, untill yesterday...
my problem:
when i solve (or skip) tutorial:
and
after i change language and restart tutorial:
and i see same hints (in same language as before), but this text is changed:
what i do wrong?
i call intro.js so:
Start It!
and options:
$scope.IntroOptions = {
steps: [{
element: '.el1',
intro: $translate.instant('text1'),
position: 'bottom'
}, {
element: '.el2',
intro: $translate.instant('text2'),
position: 'bottom'
}],
showStepNumbers: false,
showProgress: false,
exitOnOverlayClick: false,
keyboardNavigation: false,
exitOnEsc: false,
prevLabel: '',
skipLabel: '<strong>skip</strong>',
doneLabel: '<strong>skip</strong>'
};
and whole angularjs directive of intro.js:
var ngIntroDirective = angular.module('angular-intro', []);
ngIntroDirective.directive('ngIntroOptions', ['$timeout', function ($timeout) {
return {
restrict: 'A',
scope: {
ngIntroMethod: "=",
ngIntroExitMethod: "=?",
ngIntroOptions: '=',
ngIntroOncomplete: '=',
ngIntroOnexit: '=',
ngIntroOnchange: '=',
ngIntroOnbeforechange: '=',
ngIntroOnafterchange: '=',
ngIntroAutostart: '&',
ngIntroAutorefresh: '='
},
link: function(scope, element, attrs) {
var intro;
scope.ngIntroMethod = function(step) {
var navigationWatch = scope.$on('$locationChangeStart', function(){
intro.exit();
});
if (typeof(step) === 'string') {
intro = introJs(step);
} else {
intro = introJs();
}
intro.setOptions(scope.ngIntroOptions);
if (scope.ngIntroAutorefresh) {
scope.$watch(function(){
intro.refresh();
});
}
if (scope.ngIntroOncomplete) {
intro.oncomplete(function() {
scope.ngIntroOncomplete.call(this, scope);
$timeout(function() {scope.$digest()});
navigationWatch();
});
}
if (scope.ngIntroOnexit) {
intro.onexit(function() {
scope.ngIntroOnexit.call(this, scope);
$timeout(function() {scope.$digest()});
navigationWatch();
});
}
if (scope.ngIntroOnchange) {
intro.onchange(function(targetElement){
scope.ngIntroOnchange.call(this, targetElement, scope);
$timeout(function() {scope.$digest()});
});
}
if (scope.ngIntroOnbeforechange) {
intro.onbeforechange(function(targetElement) {
scope.ngIntroOnbeforechange.call(this, targetElement, scope);
$timeout(function() {scope.$digest()});
});
}
if (scope.ngIntroOnafterchange) {
intro.onafterchange(function(targetElement){
scope.ngIntroOnafterchange.call(this, targetElement, scope);
$timeout(function() {scope.$digest()});
});
}
if (typeof(step) === 'number') {
intro.goToStep(step).start();
} else {
intro.start();
}
};
scope.ngIntroExitMethod = function (callback) {
intro.exit(); //TODO check callBack
};
if (scope.ngIntroAutostart()) {
$timeout(function() {
scope.ngIntroMethod();
});
}
}
};
}]);
what i do wrong? why my hints are not changing their language?
plunker you could check here:
http://plnkr.co/edit/RsJ29a49soZ4q33gxQhk?p=preview
what i do wrong with angular-translate?

You're using the synchronous $translate.instant() which means that your intro property will never update itself when changing language.
You need to manually reload the intro.js configuration (your steps) when the language change. For that you can use angular-translate events like $translateChangeSuccess:
$rootScope.$on('$translateChangeSuccess', function() {
// updating steps config with $translate.instant() function
$scope.IntroOptions.steps = [{
element: '.el1',
intro: $translate.instant('text1'),
position: 'bottom'
}, [...]];
});

Related

directive without scope setting value undefined

In my angular app, I've to use different directives on a single field.
here are my directives
angular.module('app')
.directive('focusMe', function ($timeout) {
return {
scope: { trigger: '#focusMe' },
link: function (scope, element, attr) {
scope.$watch('trigger', function (value) {
if (value === "true") {
$timeout(function () {
element[0].focus();
});
}
});
}
};
});
and another one for datepicker
.directive('ngDatepicker', function() {
return {
restrict: 'A',
replace: true,
scope: {
ngOptions: '=',
ngModel: '='
},
template: "<div class=\"input-append date\">\n <input type=\"text\"><span class=\"add-on\"><i class=\"icon-th\"></i></span>\n</div>",
link: function(scope, element) {
scope.inputHasFocus = false;
element.datepicker(scope.ngOptions).on('changeDate', function(e) {
var defaultFormat, defaultLanguage, format, language;
defaultFormat = $.fn.datepicker.defaults.format;
format = scope.ngOptions.format || defaultFormat;
defaultLanguage = $.fn.datepicker.defaults.language;
language = scope.ngOptions.language || defaultLanguage;
return scope.$apply(function() {
return scope.ngModel = $.fn.datepicker.DPGlobal.formatDate(e.date, format, language);
});
});
element.find('input').on('focus', function() {
return scope.inputHasFocus = true;
}).on('blur', function() {
return scope.inputHasFocus = false;
});
return scope.$watch('ngModel', function(newValue) {
if (!scope.inputHasFocus) {
return element.datepicker('update', newValue);
}
});
}
};
});
it throws me an error
Multiple directives asking for new/isolated scope
after hours of searching and working on different solutions, i finally understand this one same like my problem and changed my first directive like this
angular.module('app')
.directive('focusMe', function ($timeout) {
return {
link: function (scope, element, attr) {
scope.$watch(attr.focusMe, function (value) {
if (value === "true") {
$timeout(function () {
element[0].focus();
});
}
});
}
};
})
But now its not working because it always gives me value = undefined and I don't know why it is happening. May be I'm missing something here or not doing it properly??
here is my html where I'm binding its value
<input type="text" focus-me="{{ DateOfBirthFocus }}" ng-datepicker>
Any kind of help will be appreciated.

How to work around AngularJS Directive with TypeScript

I have the following script with dateTimePicker as a AngularJS directive:
(function () {
'use strict';
var module = angular.module('feedApp');
module.directive('datetimepicker', [
'$timeout',
function ($timeout) {
return {
require: '?ngModel',
restrict: 'EA',
scope: {
options: '#',
onChange: '&',
onClick: '&'
},
link: function ($scope, $element, $attrs, controller) {
$($element).on('dp.change', function () {
$timeout(function () {
var dtp = $($element).data('DateTimePicker');
controller.$setViewValue(dtp.date().format('MM/DD/YYYY HH:mm'));
$scope.onChange();
});
});
$($element).on('click', function () {
$scope.onClick();
});
controller.$render = function () {
if (!!controller) {
if (controller.$viewValue === undefined) {
controller.$viewValue = null;
}
else if (!(controller.$viewValue instanceof moment)) {
controller.$viewValue = moment(controller.$viewValue);
}
$($element).data('DateTimePicker').date(controller.$viewValue);
}
};
$($element).datetimepicker($scope.$eval($attrs.options));
}
};
}
]);
})();
And I want to rewrite it using TypeScript. This is the code I have for now:
namespace feedApp {
'use strict';
DateTimePicker.$inject = ['$timeout'];
function DateTimePicker($timeout: ng.ITimeoutService): ng.IDirective {
var directive = <ng.IDirective>{
require: '?ngModel',
restrict: 'EA',
scope: {
options: '#',
onChange: '&',
onClick: '&'
},
link: link
};
function link(scope: ng.IScope, element: JQuery, attrs: ng.IAttributes, controller: ng.INgModelController): void {
$(element).on('dp.change', function () {
$timeout(function () {
var dtp = $(element).data('DateTimePicker');
controller.$setViewValue(dtp.date().format('MM/DD/YYYY HH:mm'));
scope.onChange();
});
});
$(element).on('click', function () {
scope.onClick();
});
controller.$render = function () {
if (!!controller) {
if (controller.$viewValue === undefined) {
controller.$viewValue = null;
}
else if (!(controller.$viewValue instanceof moment)) {
controller.$viewValue = moment(controller.$viewValue);
}
$(element).data('DateTimePicker').date(controller.$viewValue);
}
};
$(element).datetimepicker(scope.$eval(attrs.options));
}
return directive;
}
angular.module("feedApp")
.directive("datetimepicker", DateTimePicker);
}
I'm not experienced with TypeScript and I don't really know what is the best practise to change jQuery elements like $(element).on using TS. How to work with
scope: {
options: '#',
onChange: '&',
onClick: '&'
},
inside TypeScript link function? Thank you in advance for any help.
Use bindToController to access scope variables inside directive controller.
For better structuring/understanding please declare different components of a directive in different files like controller should be in an unique file, html should have its own file etc.
Please refer Directive testing question and typescript github sample project for more info regarding it.
If you required more clarification, please do tell me.
Regards
Ajay

ui-tinymce directive used into custom directive template : ngModel not updated

I currently have an issue when I call ui-tinymce directive in a custom directive. The custom directive is used to load dynamically links from backend for tinymce advlink plugin (+ load tinymce options object associated with a key passed as an attribute to the directive).
Here is my controller :
module.controller('Ctrl', function ($scope) {
$scope.test = {
val: "gfsgfdgh"
};
});
Here is how I call the directive in HTML:
<tinymce-custom type="minimal" ng-model="test.val"></tinymce-custom>`
And here is my directive :
module.directive('tinymceCustom', function($location, TinyService, Module, GenerateurPage) {
return {
restrict: 'E',
replace: true,
require:"ngModel",
scope: {
ngModel: '='
},
link: function(scope, element, attrs, ngModel){
scope.loaded = {
modules: false,
pages: false,
tinymce: false
};
scope.tinyOptions = {};
var link_list = [];
var modules = [];
var pages = [];
Module.findByOrganisme({}, function (data) {
data.forEach(function(module) {
modules.push({title: module.libelle, value: "/modules/"+module.id});
});
link_list.push({title: "Modules", menu: modules});
scope.loaded.modules = true;
initTiny();
});
GenerateurPage.findByOrganisme({}, function(data) {
data.forEach(function(page) {
pages.push({title: page.titre, value: "/#/generateurPage/afficherPage?id=/"+page.id});
});
link_list.push({title: "Pages", menu: pages});
scope.loaded.pages = true;
initTiny();
});
function initTiny() {
if (!scope.loaded.modules || !scope.loaded.pages) {
return false;
}
scope.tinyOptions = TinyService.options(attrs.type);
console.log(scope);
scope.tinyOptions.link_list = link_list;
scope.loaded.tinymce = true;
}
},
template: '<div ng-if="loaded.tinymce"><textarea ui-tinymce="tinyOptions" ng-model="ngModel"></textarea></div>'
};
});
The problem is that the model passed to ui-tinymce directive is not updated when changing the text with the editor, and the text in the editor is not updated when the model from the controller is changed... BUT, the initial ngModel value is passed to ui-tinymce directive, so I think that is the data binding that is broken. Tried to watch it with $watch but nothing happens.
I can't figure how to fix it so I'm now looking for some help...
Thx
Finaly fixed it changing the approach :
<textarea tinymce-custom="minimal" ng-model="myVar"></textarea >
The final directive :
module.directive('tinymceCustom', function($location, $compile, $q, TinyService, Module, GenerateurPage) {
return {
restrict: 'A',
priority:999,
terminal:true, // prevent lower priority directives to compile after it
scope: true,
require: ['?ngModel'],
link: function(scope, el, attrs) {
// default is basic template
var type = attrs.tinymceCustom ? attrs.tinymceCustom : 'basic';
function loadTinyOptions(name) {
var loaded = {
modules: false,
pages: false,
tinymce: false
};
var link_list = [];
var deferred = $q.defer();
var initTiny = function() {
if (!loaded.modules || !loaded.pages) {
return false;
}
var tinyOptions = TinyService.options(name);
tinyOptions.link_list = link_list;
deferred.resolve(tinyOptions);
};
Module.findByOrganisme({}, function (data) {
var modules = [];
data.forEach(function(module) {
modules.push({title: module.libelle, value: "/modules/"+module.id});
});
link_list.push({title: "Modules", menu: modules});
loaded.modules = true;
initTiny();
});
GenerateurPage.findByOrganisme({}, function(data) {
var pages = [];
data.forEach(function(page) {
pages.push({title: page.titre, value: "/#/generateurPage/afficherPage?id=/"+page.id});
});
link_list.push({title: "Pages", menu: pages});
loaded.pages = true;
initTiny();
});
return deferred.promise;
}
loadTinyOptions(type).then(function(data) {
scope._tinyOptions = data;
el.removeAttr('tinymce-custom'); // necessary to avoid infinite compile loop
el.attr('ui-tinymce', '{{_tinyOptions}}');
$compile(el)(scope);
});
}
};
Hope this can help.

tooltip data coming from api not initialized

I am created tooltip directive. but data comes from api , so on page load directive initializes and then data is loaded through api.api data comes later hence directive is not initialized properly .
how can i handle this ?
HTML::
<strong href="#" pop-over items="result" title="detailes" display-length="5">{[{ result.name}]}
Javascript :
listingApp.directive('popOver', function ($compile) {
var itemsTemplate = "<ul class='unstyled'><li>{{items}}</li></ul>";
var getTemplate = function (contentType) {
var template = '';
switch (contentType) {
case 'items':
template = itemsTemplate;
break;
}
return template;
}
return {
restrict: "A",
transclude: true,
scope: {
items: '=',
title: '#',
//popContent: '=',
displayLength:'#'
},
transclude: true,
template: "<span ng-transclude></span>",
link: function (scope, element, attrs) {
debugger;
// console.log('^^^^^^^^^^^^^^^'+attrs.popContent);
var popOverContent;
if (scope.items) {
var html = getTemplate("items");
popOverContent = $compile(html)(scope);
}
var options = {
content: popOverContent,
placement: "bottom",
html: true,
title: scope.title,
trigger: "hover"
};
if ((scope.items || '').length > attrs.displayLength) {
$(element).find('.display_data').text(scope.items.substring(0, attrs.displayLength));
element.attr('title', scope.items);
$(element).find('.view_more').popover(options);
} else {
$(element).find('.display_data').text(scope.items);
}
}
};
});
try wrapping your .popover plugin with a $timeout:
$timeout(function() {
$(element).find('.view_more').popover(options);
});

How to handle document click and notify other controllers using AngularJS?

I have created a horizontal drop down menu using AngularJS.
The menu section is managed by an angular controller called menuController. Standard menu behavior is implemented, so that on hover main menu item gets highlighted unless it is disabled. On clicking the main menu item, the sub menu toggles. If Sub menu is in a open state, I want it to go away when user clicks anywhere else on the document. I tried to create a directive to listen for document click event but not sure on how to notify menu-controller about it. How should I implement this scenario in a AngularJS way?
Partially working Original Plunk without document click handling mechanism.
UPDATE:
Based on answered suggestion, I went with Brodcast approach and updated the script to reflect my latest changes. It is working as per my expectation. I made the globalController $broadcast a message and menuController subscribe to that message.
UPDATE 2: Modified code to inject global events definition data.
var eventDefs = (function() {
return {
common_changenotification_on_document_click: 'common.changenotification.on.document.click'
};
}());
var changeNotificationApp = angular.module('changeNotificationApp', []);
changeNotificationApp.value('appEvents', eventDefs);
changeNotificationApp.directive("onGlobalClick", ['$document', '$parse',
function($document, $parse) {
return {
restrict: 'A',
link: function($scope, $element, $attributes) {
var scopeExpression = $attributes.onGlobalClick;
var invoker = $parse(scopeExpression);
$document.on("click",
function(event) {
$scope.$apply(function() {
invoker($scope, {
$event: event
});
});
}
);
}
};
}
]);
changeNotificationApp.controller("globalController", ['$scope', 'appEvents',
function($scope, appEvents) {
$scope.handleClick = function(event) {
$scope.$broadcast(appEvents.common_changenotification_on_document_click, {
target: event.target
});
};
}
]);
//menu-controller.js
changeNotificationApp.controller('menuController', ['$scope', '$window', 'appEvents',
function($scope, $window, appEvents) {
$scope.IsLocalMenuClicked = false;
$scope.menu = [{
Name: "INTEGRATION",
Tag: "integration",
IsDisabled: false,
IsSelected: false,
SubMenu: [{
Name: "SRC Messages",
Tag: "ncs-notifications",
IsDisabled: false,
AspNetMvcController: "SearchSRCMessages"
}, {
Name: "Target Messages",
Tag: "advisor-notifications",
IsDisabled: false,
AspNetMvcController: "SearchTaregtMessages"
}]
}, {
Name: "AUDITING",
Tag: "auditing",
IsDisabled: true,
IsSelected: false,
SubMenu: []
}];
$scope.appInfo = {
Version: "1.0.0.0",
User: "VB",
Server: "azzcvy0623401v",
IsSelected: false
};
var resetMenu = function() {
angular.forEach($scope.menu, function(item) {
item.IsSelected = false;
});
$scope.appInfo.IsSelected = false;
};
$scope.toggleDropDownMenu = function(menuItem) {
var currentDropDownState = menuItem.IsSelected;
resetMenu($scope.menu, $scope.appInfo);
menuItem.IsSelected = !currentDropDownState;
$scope.IsLocalMenuClicked = true;
};
$scope.loadPage = function(menuItem) {
if (menuItem.AspNetMvcController)
$window.location.href = menuItem.AspNetMvcController;
};
$scope.$on(appEvents.common_changenotification_on_document_click,
function(event, data) {
if (!$scope.IsLocalMenuClicked)
resetMenu($scope.menu, $scope.appInfo);
$scope.IsLocalMenuClicked = false;
});
}
]);
UPDATE 3: Modified code in previous implementation to fix a bug where document click fires multiple times. Almost similar approach, but this time, if any one clicks again anywhere on the menu, the click is ignored. Please refer to the New Working Plunk for full code example
changeNotificationApp.directive("onGlobalClick", ['$document', '$parse',
function ($document, $parse) {
return {
restrict: 'A',
link: function ($scope, $element, $attributes) {
var scopeExpression = $attributes.onGlobalClick;
var invoker = $parse(scopeExpression);
$document.on("click",
function (event) {
var isClickedElementIsChildOfThisElement = $element.find(event.target).length > 0;
if (isClickedElementIsChildOfThisElement) return;
$scope.$apply(function () {
invoker($scope, {
$event: event
});
});
}
);
}
};
}
]);
UPDATE 4: Implemented another alternate option. Please refer to the Option 2 Plunk for full code example
var eventDefs = (function () {
return {
on_click_anywhere: 'common.changenotification.on.document.click'
};
}());
var changeNotificationApp = angular.module('changeNotificationApp', []);
changeNotificationApp.value('appEvents', eventDefs);
changeNotificationApp.directive("onClickAnywhere", ['$window', 'appEvents',
function($window, appEvents) {
return {
link: function($scope, $element) {
angular.element($window).on('click', function(e) {
// Namespacing events with name of directive + event to avoid collisions
$scope.$broadcast(appEvents.on_click_anywhere, e.target);
});
}
};
}
]);
//menu-controller.js
changeNotificationApp.controller('menuController', ['$scope', '$window', 'appEvents', '$element',
function ($scope, $window, appEvents, $element) {
$scope.menu = [
{
Name: "INTEGRATION",
Tag: "integration",
IsDisabled: false,
IsSelected: false,
SubMenu: [
{
Name: "SRC Messages",
Tag: "ncs-notifications",
IsDisabled: false,
AspNetMvcController: "SearchSRCMessages"
},
{
Name: "Target Messages",
Tag: "advisor-notifications",
IsDisabled: false,
AspNetMvcController: "SearchTaregtMessages"
}
]
},
{
Name: "AUDITING",
Tag: "auditing",
IsDisabled: true,
IsSelected: false,
SubMenu: []
}
];
$scope.appInfo = {
Version: "1.0.0.0",
User: "VB",
Server: "azzcvy0623401v",
IsSelected: false
};
var resetMenu = function () {
angular.forEach($scope.menu, function (item) {
item.IsSelected = false;
});
$scope.appInfo.IsSelected = false;
};
$scope.toggleDropDownMenu = function (menuItem) {
var currentDropDownState = menuItem.IsSelected;
resetMenu($scope.menu, $scope.appInfo);
menuItem.IsSelected = !currentDropDownState;
};
$scope.loadPage = function (menuItem) {
if (menuItem.AspNetMvcController)
$window.location.href = menuItem.AspNetMvcController;
};
$scope.$on(appEvents.on_click_anywhere, function(event, targetElement) {
var isClickedElementIsChildOfThisElement = $element.find(targetElement).length > 0;
if (isClickedElementIsChildOfThisElement) return;
$scope.$apply(function(){
resetMenu($scope.menu, $scope.appInfo);
});
});
}
]);
You can simplify the directive into something like this:
changeNotificationApp.directive('onDocumentClick', ['$document',
function($document) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var onClick = function() {
scope.$apply(function() {
scope.$eval(attrs.onDocumentClick);
});
};
$document.on('click', onClick);
scope.$on('$destroy', function() {
$document.off('click', onClick);
});
}
};
}
]);
And then pass a function from the menuController to it:
<section class="local-nav" ng-controller="menuController" on-document-click="someFunction()">
No need for the globalController this way.
If you want to keep the globalController and handle it from there, you can:
1.) Make the menu into a service and then inject it into all controllers that need to be able to control it.
2.) Broadcast an event from globalController and listen for it in menuController.
Specific alternative solution: You can turn the directive into a 'on-outside-element-click' and use it like this:
<ul on-outside-element-click="closeMenus()">
The directive looks like this and will only call closeMenus() if you click outside the ul:
changeNotificationApp.directive('onOutsideElementClick', ['$document',
function($document) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
element.on('click', function(e) {
e.stopPropagation();
});
var onClick = function() {
scope.$apply(function() {
scope.$eval(attrs.onOutsideElementClick);
});
};
$document.on('click', onClick);
scope.$on('$destroy', function() {
$document.off('click', onClick);
});
}
};
}
]);
Working Plunker: http://plnkr.co/edit/zVo0fL2wOCQb3eAUx44U?p=preview
Well you have done things well. If you apply the same directive over the menuController
<section class="local-nav" ng-controller="menuController" on-global-click="handleClick($event)>
and have the click handler defined in your menuController you are all set to go.
I don't think there is any harm in having multiple handlers for the event on document. So where ever you define this directive that element can respond to the global document click event.
Update: As i tested this, it leads to another problem where this method get called, where ever you click on the page. You need a mechanism to differentiate now.

Categories

Resources