After adding angular-ui-bootstrap and running grunt serve on my yeoman app, it runs perfectly and the modal I want to show is displayed correctly, but once I do a grunt build, I get an unknown provider error in my console.
<!-- This is what I added in my index.html -->
<script src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script>
// In app.js I have
angular.module('yeomanApp', [
'ngCookies',
'ngResource',
'ngSanitize',
'ngRoute',
'ui.bootstrap'
])
and in the controller,
.controller('myCntrl', function ($modal) {
$scope.items = ['item1', 'item2', 'item3'];
$scope.showDeleteWarning = function () {
var modalInstance = $modal.open({
templateUrl: 'deleteWarning.html',
controller: ModalInstanceCtrl,
resolve: {
items: function () {
return $scope.items;
}
}
});
modalInstance.result.then(function (selectedItem) {
$scope.selected = selectedItem;
}, function () {});
};
// Please note that $modalInstance represents a modal window (instance) dependency.
// It is not the same as the $modal service used above.
var ModalInstanceCtrl = function ($scope, $modalInstance, items) {
$scope.items = items;
$scope.selected = {
item: $scope.items[0]
};
$scope.ok = function () {
$modalInstance.close($scope.selected.item);
deleteVent();
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
};
};
Likely that you need to inject your controller dependency...
https://docs.angularjs.org/tutorial/step_05#a-note-on-minfication
.controller('myCntrl', ['$modal', function ($modal) {
/* Controller Code Here... */
}]);
I know this is an old question, but I'll post my answer here for people who come across this problem in the future.
I came across this exact problem before. The cause of your errors during minification is most likely your 'var ModalInstanceCtrl'.
Here's how I got my code to work:
var modalInstance = $modal.open({
templateUrl: 'deleteWarning.html',
controller: 'ModalInstanceCtrl', //change this to a string
resolve: {
items: function () {
return $scope.items;
}
}
});
and this line:
var ModalInstanceCtrl = function ($scope, $modalInstance, items) {
to:
angular.module('myModule').controller('ModalInstanceCtrl', function ($scope, $modalInstance, items) {
For anyone who just encountered this problem, maybe this will help.
We use customModalDefaults and customModalOptions, so we had to turn the whole return $modal.open(tempModalDefaults).result; in the show function to the following:
this.show = function (customModalDefaults, customModalOptions) {
//Create temp objects to work with since we're in a singleton service
var tempModalDefaults = {};
var tempModalOptions = {};
//Map angular-ui modal custom defaults to modal defaults defined in service
angular.extend(tempModalDefaults, modalDefaults, customModalDefaults);
//Map modal.html $scope custom properties to defaults defined in service
angular.extend(tempModalOptions, modalOptions, customModalOptions);
return $modal.open({
backdrop: customModalDefaults.backdrop,
keyboard: customModalDefaults.keyboard,
modalFade: customModalDefaults.modalFade,
templateUrl: customModalDefaults.templateUrl,
size: customModalDefaults.size,
controller: ['$scope', '$modalInstance', function ($scope, $modalInstance) {
$scope.modalOptions = tempModalOptions;
$scope.modalOptions.ok = function (result) {
$modalInstance.close(result);
};
$scope.modalOptions.close = function (result) {
$modalInstance.dismiss('cancel');
};
} ]
}).result;
};
I just ran into this problem on only one of many modals used throughout my application, and it turned out my problem was not using explicit function annotation in the resolve block of the modal configuration.
var modalInstance = $uibModal.open({
templateUrl: 'preferences.html',
controller: 'preferencesCtrl as ctrl', // this external controller was using explicit function annotation...
resolve: {
parent: [function() {
return ctrl;
}],
sectorList: ['preferencesService', function(preferencesService) { // but this was not!
return preferencesService.getSectors();
}]
}
});
Hope this saves someone else a gray hair or two...
Related
I've been searching a long time for an answer to the question I'm about to ask without success.
Let's say I have the following directive :
(function () {
'use strict';
angular
.module('com.acme.mymod')
.directive('myDirective', myDirective);
function myDirective() {
var directive = {
restrict: 'EA',
scope: {},
replace: true,
templateUrl: 'folder/myDirective/myDirective.tpl.html',
controller: "myDirectiveController",
controllerAs: 'vm',
bindToController: {
somedata: "#?",
endpoint: "#?"
},
link: link
};
return directive;
function link(scope, element) {
activate();
function activate() {
scope.$on('$destroy', destroy);
element.on('$destroy', destroy);
}
}
function destroy() {
}
}
})();
myDirectiveController is as follow:
(function () {
'use strict';
angular
.module('com.acme.mymod')
.controller('myDirectiveController', myDirectiveController);
myDirectiveController.$inject = ['utils', '$log'];
// utils is an external library factory in another module
function myDirectiveController(utils, $log) {
var vm = this;
vm.menuIsOpen = false;
function activate() {
vm.dataPromise = utils.getValuePromise(null, vm.somedata, vm.endpoint);
vm.dataPromise.then(function (result) {
vm.data = result.data;
}, function () {
$log.debug("data is not Valid");
});
}
activate();
}
})();
The spec file is as follow:
describe('myDirective Spec', function () {
'use strict';
angular.module('com.acme.mymod', []);
var compile, scope, directiveElem, utils;
beforeEach(function(){
module('com.acme.mymod');
inject(function($compile, $rootScope, $injector,utils){
compile = $compile;
scope = $rootScope.$new();
scope.$digest();
directiveElem = getCompiledElement();
//utils=utils;
console.log(utils.test());
});
});
function getCompiledElement(){
var element = angular.element('<div my-directive="" data-some-data=\' lorem\'></div>');
var compiledElement = compile(element)(scope);
scope.$digest();
return compiledElement;
}
it('should have a nav element of class', function () {
var navElement = directiveElem.find('nav');
expect(navElement.attr('class')).toBeDefined();
});
it('should have a valid json data-model' ,function () {
var data=directiveElem.attr('data-somedata');
var validJSON=false;
try{
validJSON=JSON.parse(dataNav);
}
catch(e){
}
expect(validJSON).toBeTruthy();
});
});
What I can't quite figure out is that every test I try to run, the directive is not compiled or created correctly I'm not sure.
I get :
Error: [$injector:unpr] Unknown provider: utilsProvider <- utils
I tried:
Injecting the controller or utils with $injector.get()
Looked at this post $injector:unpr Unknown provider error when testing with Jasmine
Any tips, pointers or clues as to what I'm doing wrong would be more than welcome
I found the solution after feeling a hitch in my brain :)
In the beforeEach function, all I needed to do is to reference my utils module name this way:
module('com.acme.myutilsmod');
This line "expose" modules components so consumers can use it.
I'm building a wagtail / django app using requirejs as js assets combiner, for the site front end.
I'm using it because I've ever been in a kind of JS dependencies hell, where nothing works because of multiple versions of same libs loaded, from different django apps... (I don't even know if it is a good solution)
I've to tell that I'm not a JS expert, and I've none arround me :(
I'm using the good old templates to render the pages, not using angular, react, riot nor vue : I'm a pretty old school dev :)
I've already adapted some scripts to use require, but I'm stuck for now...
I've installed the django_select2 application, and I'm trying to adapt the django_select2.js asset.
I've loaded select2 through bower, and I've updaetd my config.js:
"shim": {
select2: {
deps: ["jquery"],
exports: "$.fn.select2"
}
},
paths: {
...
select2: "select2/dist/js/select2"
}
Then I'm trying to adapt the django_select2.js:
require(['jquery', 'select2'], function ($, select2) {
return (function ($) {
var init = function ($element, options) {
$element.select2(options);
};
var initHeavy = function ($element, options) {
var settings = $.extend({
ajax: {
data: function (params) {
var result = {
term: params.term,
page: params.page,
field_id: $element.data('field_id')
}
var dependentFields = $element.data('select2-dependent-fields')
if (dependentFields) {
dependentFields = dependentFields.trim().split(/\s+/)
$.each(dependentFields, function (i, dependentField) {
result[dependentField] = $('[name=' + dependentField + ']', $element.closest('form')).val()
})
}
return result
},
processResults: function (data, page) {
return {
results: data.results,
pagination: {
more: data.more
}
}
}
}
}, options);
$element.select2(settings);
};
$.fn.djangoSelect2 = function (options) {
var settings = $.extend({}, options);
$.each(this, function (i, element) {
var $element = $(element);
if ($element.hasClass('django-select2-heavy')) {
initHeavy($element, settings);
} else {
init($element, settings);
}
});
return this;
};
$(function () {
$('.django-select2').djangoSelect2();
});
}($));
});
I'm having a Mismatched anonymous define() when running my page in the browser...
I'me realy not a JS expert, I'm coding by trial and error... Could anyone help me with this ?
Thanks !
OK, I have an auto-response...
I've inherited the mixin:
class _Select2Mixin(Select2Mixin):
def _get_media(self):
"""
Construct Media as a dynamic property.
.. Note:: For more information visit
https://docs.djangoproject.com/en/1.8/topics/forms/media/#media-as-a-dynamic-property
"""
return forms.Media(js=('django_select2/django_select2.js', ),
css={'screen': (settings.SELECT2_CSS,)})
media = property(_get_media)
class _Select2MultipleWidget(_Select2Mixin, forms.SelectMultiple):
pass
Then I can use the widget:
class DocumentationSearchForm(forms.Form):
...
document_domains = forms.ModelMultipleChoiceField(required=False,
label=_('Document domains'),
queryset=NotImplemented,
widget=_Select2MultipleWidget)
I've set my config.js file for path:
requirejs.config({
paths: {
jquery: 'jquery/dist/jquery',
select2: "select2/dist/js"
},
shim: {
"select2/select2": {
deps: ["jquery"],
exports: "$.fn.select2"
}
}
});
Then I've overridden the django_select2.js file, to wrap the content in a require satement:
require(['jquery', 'select2/select2'], function ($) {
(function ($) {
var init = function ($element, options) {
$element.select2(options);
};
var initHeavy = function ($element, options) {
var settings = $.extend({
ajax: {
data: function (params) {
var result = {
term: params.term,
page: params.page,
field_id: $element.data('field_id')
}
var dependentFields = $element.data('select2-dependent-fields')
if (dependentFields) {
dependentFields = dependentFields.trim().split(/\s+/)
$.each(dependentFields, function (i, dependentField) {
result[dependentField] = $('[name=' + dependentField + ']', $element.closest('form')).val()
})
}
return result
},
processResults: function (data, page) {
return {
results: data.results,
pagination: {
more: data.more
}
}
}
}
}, options);
$element.select2(settings);
};
$.fn.djangoSelect2 = function (options) {
var settings = $.extend({}, options);
$.each(this, function (i, element) {
var $element = $(element);
if ($element.hasClass('django-select2-heavy')) {
initHeavy($element, settings);
} else {
init($element, settings);
}
});
return this;
};
$(function () {
$('.django-select2').djangoSelect2();
});
}($));
});
That's all, folks !
I am using Twitter Typeahead.js in a subcomponent in Ember which I feed a dataSource function (see below).
This dataSource function queries a remote server. This query I would like to have debounced in Ember which does not seem to work.
Does this have to do with the runloop? Anything I should wrap?
import Ember from 'ember';
export default Ember.Component.extend({
dataResponse: [],
dataSource: function () {
var component = this;
// function given to typeahead.js
return function (query, cb) {
var requestFunc = function () {
var encQuery = encodeURIComponent(query);
Ember.$.getJSON('/api/autocompletion?prefix=' + encQuery).then(function (result) {
// save results
component.set('dataResponse', result.autocompletion);
// map results
var mappedResult = Ember.$.map(result.autocompletion, function (item) {
return { value: item };
});
cb(mappedResult);
});
};
// this is not debounced, why? :|
Ember.run.debounce(this, requestFunc, 500); // debounce by 500ms
};
}.property()
});
Note: I do not use Bloodhound with Typeahead.js since I need access to the results. A custom solution seemed easier at first.
Debounce works by creating a unique key based on the context/function. When you call it subsequent times it compares the existing keys to the context/function key passed in. You are passing in a different function every time you call debounce, which is why it isn't working how you're expecting it to work.
Taking the advice from #Kingpin2k I refactored the code like this:
import Ember from 'ember';
export default Ember.Component.extend({
dataResponse: [],
dataSource: function () {
var component = this;
var queryString = null;
var callBack = null;
var requestFunc = function () {
var encQuery = encodeURIComponent(queryString);
Ember.$.getJSON('/api/autocompletion?prefix=' + encQuery).then(function (result) {
// save results
component.set('dataResponse', result.autocompletion);
var mappedResult = Ember.$.map(result.autocompletion, function (item) {
return { value: item };
});
callBack(mappedResult);
});
};
// function used for typeahead
return function (q, cb) {
queryString = q;
callBack = cb;
Ember.run.debounce(this, requestFunc, 500); // debounce by 500ms
};
}.property()
});
Please look at this code...
```
App.BooksRoute = Ember.Route.extend({
model: return function () {
return this.store.find('books');
}
});
App.BooksController = Ember.ArrayController.extend({
actions: {
updateData: function () {
console.log("updateData is called!");
var books = this.filter(function () {
return true;
});
for(var i=0; i<books.length; i++) {
//doSomething…
}
}
}
});
```
I want to call the updateData action on BooksController from the outside.
I tried this code.
App.__container__.lookup("controller:books").send('updateData');
It works actually. But, in the updateData action, the this is different from the one in which updateData was called by clicking {{action 'updateData'}} on books template.
In the case of clicking {{action 'updateData'}}, the this.filter() method in updateData action will return books models.
But, In the case of calling App.__container__.lookup("controller:books").send('updateData');, the this.filter() method in updateData action will return nothing.
How do I call the updateData action on BooksController from the outside, with the same behavior by clicking {{action 'updateData'}}.
I would appreciate knowing about it.
(I'm using Ember.js 1.0.0)
You can use either bind or jQuery.proxy. bind is provided in JS since version 1.8.5, so it's pretty safe to use unless you need to support very old browsers. http://kangax.github.io/es5-compat-table/
Either way, you're basically manually scoping the this object.
So, if you have this IndexController, and you wanted to trigger raiseAlert from outside the app.
App.IndexController = Ember.ArrayController.extend({
testValue : "fooBar!",
actions : {
raiseAlert : function(source){
alert( source + " " + this.get('testValue') );
}
}
});
With bind :
function externalAlertBind(){
var controller = App.__container__.lookup("controller:index");
var boundSend = controller.send.bind(controller);
boundSend('raiseAlert','External Bind');
}
With jQuery.proxy
function externalAlertProxy(){
var controller = App.__container__.lookup("controller:index");
var proxySend = jQuery.proxy(controller.send,controller);
proxySend('raiseAlert','External Proxy');
}
Interestingly this seems to be OK without using either bind or proxy in this JSBin.
function externalAlert(){
var controller = App.__container__.lookup("controller:index");
controller.send('raiseAlert','External');
}
Here's a JSBin showing all of these: http://jsbin.com/ucanam/1080/edit
[UPDATE] : Another JSBin that calls filter in the action : http://jsbin.com/ucanam/1082/edit
[UPDATE 2] : I got things to work by looking up "controller:booksIndex" instead of "controller:books-index".
Here's a JSBin : http://jsbin.com/ICaMimo/1/edit
And the way to see it work (since the routes are weird) : http://jsbin.com/ICaMimo/1#/index
This solved my similar issue
Read more about action boubling here: http://emberjs.com/guides/templates/actions/#toc_action-bubbling
SpeedMind.ApplicationRoute = Ember.Route.extend({
actions: {
// This makes sure that all calls to the {{action 'goBack'}}
// in the end is run by the application-controllers implementation
// using the boubling action system. (controller->route->parentroutes)
goBack: function() {
this.controllerFor('application').send('goBack');
}
},
};
SpeedMind.ApplicationController = Ember.Controller.extend({
actions: {
goBack: function(){
console.log("This is the real goBack method definition!");
}
},
});
You could just have the ember action call your method rather than handling it inside of the action itself.
App.BooksController = Ember.ArrayController.extend({
actions: {
fireUpdateData: function(){
App.BooksController.updateData();
}
},
// This is outside of the action
updateData: function () {
console.log("updateData is called!");
var books = this.filter(function () {
return true;
});
for(var i=0; i<books.length; i++) {
//doSomething…
}
}
});
Now whenever you want to call updateData(), just use
App.BooksController.updateData();
Or in the case of a handlebars file
{{action "fireUpdateData"}}
What is the best approach to use Disqus in a single page application?
I see that the angular js docs has implemented it successfully.
Currently our approach looks like is this in our AngularJS app, but it seems unstable, is hard to test, and loads wrong thread ids (the same thread gets loaded almost everywhere).
'use strict';
angular.module('studentportalenApp.components')
.directive('disqusComponent',['$log', '$rootScope', function($log, $rootScope) {
var _initDisqus = function _initDisqus(attrs)
{
if(window.DISQUS) {
DISQUS.reset({
reload: true,
config: function () {
this.page.identifier = attrs.threadId;
this.disqus_container_id = 'disqus_thread';
this.page.url = attrs.permalinkUrl;
}
});
}
else
{
$log.error('window.DISQUS did not exist before directive was loaded.');
}
}
//Destroy DISQUS bindings just before route change, to properly dispose of listeners and frame (postMessage nullpointer exception)
$rootScope.$on('$routeChangeStart', function() {
if(window.DISQUS) {
DISQUS.reset();
}
});
var _linkFn = function link(scope, element, attrs) {
_initDisqus(attrs);
}
return {
replace: true,
template: '<div id="disqus_thread"></div>',
link: _linkFn
};
}]);
I also wanted to include Disqus on my AngularJS-powered blog. I found the existing solutions a bit unwieldy so I wrote my own directive:
.directive('dirDisqus', function($window) {
return {
restrict: 'E',
scope: {
disqus_shortname: '#disqusShortname',
disqus_identifier: '#disqusIdentifier',
disqus_title: '#disqusTitle',
disqus_url: '#disqusUrl',
disqus_category_id: '#disqusCategoryId',
disqus_disable_mobile: '#disqusDisableMobile',
readyToBind: "#"
},
template: '<div id="disqus_thread"></div>comments powered by <span class="logo-disqus">Disqus</span>',
link: function(scope) {
scope.$watch("readyToBind", function(isReady) {
// If the directive has been called without the 'ready-to-bind' attribute, we
// set the default to "true" so that Disqus will be loaded straight away.
if ( !angular.isDefined( isReady ) ) {
isReady = "true";
}
if (scope.$eval(isReady)) {
// put the config variables into separate global vars so that the Disqus script can see them
$window.disqus_shortname = scope.disqus_shortname;
$window.disqus_identifier = scope.disqus_identifier;
$window.disqus_title = scope.disqus_title;
$window.disqus_url = scope.disqus_url;
$window.disqus_category_id = scope.disqus_category_id;
$window.disqus_disable_mobile = scope.disqus_disable_mobile;
// get the remote Disqus script and insert it into the DOM
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = '//' + scope.disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
}
});
}
};
});
Advantages
The main advantage of this approach, I think, is that it keeps things simple. Once you have registered the directive with your app, you don't need to write any JavaScript or set any config values in your JavaScript. All configuration is handled by passing attributes in the directive tag like so:
<dir-disqus disqus-shortname="YOUR_DISQUS_SHORTNAME"
disqus-identifier="{{ article.id }}"
disqus-title="{{ article.title }}"
...>
</dir-disqus>
Also, you don't need to alter your index.html file to include the Disqus .js file - the directive will dynamically load it when it is ready. This means that all that extra .js will only get loaded on those pages that actually use the Disqus directive.
You can see the full source and documentation here on GitHub
Caveat
The above will only work properly when your site is in HTML5Mode, i.e. not using the "#" in your URLs. I am updating the code on GitHub so the directive will work when not using HTML5Mode, but be warned that you must set a hashPrefix of "!" to make "hashbang" URLs - e.g. www.mysite.com/#!/page/123. This is a limitation imposed by Disqus - see http://help.disqus.com/customer/portal/articles/472107-using-disqus-on-ajax-sites
I know nothing about Disqus, but according to the AngularJS Documentation source code:
They bind a load function to afterPartialLoaded:
$scope.afterPartialLoaded = function() {
var currentPageId = $location.path();
$scope.partialTitle = $scope.currentPage.shortName;
$window._gaq.push(['_trackPageview', currentPageId]);
loadDisqus(currentPageId);
};
Then, they simply add the html to the page:
function loadDisqus(currentPageId) {
// http://docs.disqus.com/help/2/
window.disqus_shortname = 'angularjs-next';
window.disqus_identifier = currentPageId;
window.disqus_url = 'http://docs.angularjs.org' + currentPageId;
// http://docs.disqus.com/developers/universal/
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = 'http://angularjs.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] ||
document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
angular.element(document.getElementById('disqus_thread')).html('');
}
This is how we solved it.
We load DISQUS in the body of index.html, and resets it whenever there is a directive using it.
Directive:
'use strict';
angular.module('fooApp.directives')
.directive('disqusComponent',['$window', '$log', function($window, $log) {
var _initDisqus = function _initDisqus(scope)
{
if($window.DISQUS) {
$window.DISQUS.reset({
reload: true,
config: function () {
this.page.identifier = scope.threadId;
this.disqus_container_id = 'disqus_thread';
}
});
}
else
{
$log.error('window.DISQUS did not exist before directive was loaded.');
}
}
var _linkFn = function link(scope, element, attrs) {
element.html('<div id="disqus_thread"></div>');
_initDisqus(scope);
}
return {
replace: true,
template: 'false',
scope: {
threadId: '#'
},
link: _linkFn
};
}]);
This is how it can be tested:
'use strict';
describe('Directive: Disqus', function() {
var element, $window, $rootScope, $compile;
beforeEach(function() {
module('fooApp.directives', function($provide) {
$provide.decorator('$window', function($delegate) {
$delegate.DISQUS = {
reset: jasmine.createSpy()
};
return $delegate;
});
});
inject(function(_$rootScope_, _$compile_, _$window_) {
$window = _$window_;
$rootScope = _$rootScope_;
$compile = _$compile_;
});
});
it('should place a div with id disqus_thread in DOM', function() {
element = angular.element('<disqus-component></disqus-component>');
element = $compile(element)($rootScope);
expect(element.html()).toBe('<div id="disqus_thread"></div>');
});
it('should do a call to DISQUS.reset on load', function() {
element = angular.element('<disqus-component thread-id="TESTTHREAD"></disqus-component>');
element = $compile(element)($rootScope);
var resetFn = $window.DISQUS.reset;
expect(resetFn).toHaveBeenCalled();
});
});