I am using electron with angular 1.6.4.
I have a controller, in which I dynamically generate li. I want to bind a double click event on this list items, but I could not succeed.
function TheController($http, $scope, KeyService) {
$scope.openItem = function(id) {
console.log(id);
}
var key = KeyService.getLastKey();
connectToBackend($http,key);
}
function connectToBackend($http, key) {
$http.get(ENDPOINT).then(
function(result) {
//do some work
document.getElementById("list").innerHTML += `<li draggable="true" ondragstart="itemDrag(event)" id=${theID} ng-dblclick="openItem(this.id)"><i class="fa fa-folder-open"></i> ${result}</li><hr>`;
},
function(e) {
//error
}
);
}
If I double-click here, absolutely nothing happens - not even an exception.
If I use ondblclick, it works if I define openItem in renderer.js. But I'd rather like to have it defined inside TheController, to keep some order and to be able to access injected services.
Is this possible? Is the drag stuff maybe interfering?
The directly appended html wouldn't work until you compile it. You should manually compile it before injecting it into DOM tree.
document.getElementById("list")
.appendChild($compile(`
<li draggable="true" ondragstart="itemDrag(event)"
id=${theID} ng-dblclick="openItem(this.id)">
<i class="fa fa-folder-open"></i> ${result}
</li>
<hr>`)($scope);
Generally doing DOM manipulation from directly controller is anti-pattern as it makes your controller code to more tightly coupled with view/html.
Rather I'd suggest you to use ng-inlcude directive and place custom template in ng-template script. So that it will available any time inside $templateCache of angular.
<script id="myCustom.html" type="text/ng-template">
<li draggable="true" ondragstart="itemDrag(event)"
id="{{theId}}" ng-dblclick="openItem(id)">
<i class="fa fa-folder-open"></i>
<div ng-include="ENDPOINT"></div>
</li>
</script>
and then your html will look like below.
Html
<div id="list">
... Your content ..
</div>
<div ng-include="'myCustom.html'"></div>
If you noted, I directly used ENDPOINT directly inside ng-include for the same to work, you have to do some additional setting
angular.module('myApp').config(function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist([
// Allow loading from outer templates domain.
'http://somedomain.com/templates/**' //ENDPOINT domain should white listed here
]);
});
Also ondragstart wouldn't call your controllers method, until you patch it up with angular wrapper directive. There are third-party library available out there, you could use any one of them.
Related
I'm building a mobile app with Ionic using AngularJS.
In some of the views I would like to bind HTML code having multiple links, but somehow its not working on mobile.
In the browser it works just perfectly, but on mobile the link can not be clicked.
Text I would like to bind:
"Some text http://www.test.com"
My code in HTML:
<p ng-bind-html="testDetails"></p>
$sanitize is available, ngSanitize has been added as a dependency
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular-sanitize.js"></script>
var appControllers = angular.module('starter.controllers', ['ngSanitize']); // Use for all controller of application.
Any idea?
Looks I've found a solution.
Somehow simple <a> tags with href attribute does not seem to be working on mobile with ng-bind-html.
Instead, I used:
<a href="" onClick="window.open('http://www.test.com', '_system', 'location=yes')"
target="_blank">http://www.test.com</a>
This just works perfectly, but it was necessary to bypass $sanitize in ng-bind-html by explicitly trusting the dangerous value (See AngularJS documentation).
https://docs.angularjs.org/api/ngSanitize/service/$sanitize
In Controller:
$scope.testDetails = '<a href="" onClick="window.open('http://www.test.com', '_system', 'location=yes')"
target="_blank">http://www.test.com</a>'
$scope.deliberatelyTrustDangerousSnippet = function(sniptext) {
return $sce.trustAsHtml(sniptext);
};
In HTML view:
<p ng-bind-html='deliberatelyTrustDangerousSnippet(testDetails)'></p>
Also I've found a good filter to do this work, if the data is received with simple <a href="">attributes:
https://gist.github.com/rewonc/e53ad3a9d6ca704d402e
On my site I have a navbar component that I want to customize for each and every ng-view I end up loading. Currently I'm doing this as follows. I have a NavCtrl for the navbar itself and my ng-view directive sits outside the scope of this controller. And I use a navbar service to change/override functionality in the navbar e.g each of my views need to override the click handler for a save button my navbar. The NavbarService has hookes to set the save function. And in the NavCtrl the $scope.save = NavbarService.save
var app = angular.module('myApp', ['ngRoute', 'ngResource']);
app.config(function($routeProvider) {
$routeProvider.when('/world', {templateUrl : 'world.html', controller : 'WorldCtrl'}).otherwise({templateUrl : 'hello.html', controller : 'HelloCtrl'});
});
app.service('NavbarService', function() {
var message = 'Save button not clicked yet',
saveFunction = function() {
this.setMessage('Default save called');
};
this.save = function() {
_saveFunction();
};
this.setSaveFunction = function(funct) {
_saveFunction = funct;
};
this.setMessage = function(newMessage) {
message = newMessage;
};
this.getMessage = function() {
return message;
}
});
app.controller('NavCtrl', function($scope, $location, NavbarService) {
$scope.message = NavbarService.getMessage();
$scope.save = NavbarService.save;
$scope.world = function() {
$location.path('/world');
};
$scope.hello = function() {
$location.path('/hello');
};
$scope.$watch(NavbarService.getMessage, function(newValue) {
$scope.message = newValue;
});
});
app.controller('HelloCtrl', function($scope, NavbarService) {
$scope.init = function() {
NavbarService.setSaveFunction(function() {
NavbarService.setMessage('Save method called from the HelloCtrl');
});
};
});
app.controller('WorldCtrl', function($scope, NavbarService) {
$scope.init = function() {
NavbarService.setSaveFunction(function() {
NavbarService.setMessage('Save method called from the WorldCtrl');
});
};
});
<html lang="en">
<head>
<title>My App</title>
</head>
<body ng-app="myApp">
<nav ng-controller="NavCtrl">
<button ng-click="save()">Save</button>
<button ng-click="world()">Go to world</button>
<button ng-click="hello()">Go to hello</button>
<pre>{{message}}</pre>
</nav>
<div ng-view onload="init()"></div>
<script type="text/ng-template" id="hello.html">
<h2>Active view is Hello</h2>
</script>
<script type="text/ng-template" id="world.html">
<h2>Active view is World</h2>
</script>
<script src="https://code.jquery.com/jquery-2.1.4.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular-route.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular-resource.js"></script>
</body>
</html>
I'm wondering if I'm making this too complicated. I think the same thing can be achieved by just nesting the ng-view directive within the NavCtrl's scope. That way I can override $scope.save in each view controller.
But most the documentation states that services are the preferred way to share resources across controllers. Is one way better than the other? Why?
Please advise. Thanks.
However I will have to repeat the <button ng-click="save()">Save</button> element in every template if since all my views need that button.
This comment clears up a few things, and in that case I do not necessarily recommend the dynamic/declarative approach. Instead, this particular UX can be considered more of a static UX and I'd do something a little differently that still avoids any service or scope inheritance. I suggest using the following:
app level
Your app level nav can still borrow the other answer's suggestion, however let's disregard the dynamic/declarative content. So the nav looks like:
<div class="navbar navbar-submenu2">
<ul class="nav navbar-nav navbar-submenu2">
<!-- container for dynamic view-specific menu content -->
<li nx-view-menu-container class="nav navbar-nav">
</li>
<!-- static nav items -->
<li class="nav navbar-nav">
<a ng-click="saveRqst()">
<span class="current-del-name">Save</span>
</a>
</li>
The app-level controller would have this:
.controller 'appController', [
'$scope'
($scope)->
$scope.saveRqst = ->
$scope.$root.$broadcast 'saveRqst', <addt. data if applicable>
]
per view level controller
This makes a few assumptions:
only on of these types of views are ever present (meaning it's one view/controller per application state, otherwise you might have 2 controllers trying to process a save rqst)
your views reside as a child within your application template
your views adhere to the normal view controller paradigm (meaning they're not special directives with isolate scopes)
.controller 'someController', [
'$scope'
($scope)->
$scope.$on 'saveRqst`, (data)-> #do something for this view
]
Now I know this looks kinda like the scope inheritance bit, but it's not. You have to define your save logic per view controller logic anyway, there's no getting around that if I've understood what you've presented in the question and other answer's comment. The one nice bit about this event bus approach is it's easy enough to track the logic. If you notate your code, then another developer who's had any experience working with Java or Flex or whatever, will easily be able to get up to speed on implementing additional views w. save logic.
So I've presented 2 approaches to this. One is the more dynamic, declarative approach and this one is the more static, yet unique-per-view approach.
preface & some history
Let me propose something that utilizes neither a service, nor scope inheritance. Here is a similar issue I was trying to tackle. I have views that get loaded via ui.router's ui-view (we're basically talking application states here right?). I have my normal application nav & subnav, something like this...
['findings', 'tasks', 'reports', 'blah']
...that are common to all views. But I also have some specific UI per view that I want wired to that view's $scope/controller. In other words, I may have a dropdown specific to the current view, and a typeahead for another view. I elected to tackle this declaratively with a directive rather than trying to force some inheritance override paradigm.
directives
nxViewMenuContainer
First off I made a directive that was simply a container for housing the declared elements specific the views' nav elements.
.directive 'nxViewMenuContainer', [
()->
dir =
restrict: 'EAC'
link: ($scope, elem, attrs)->
if !elem.attr 'id'
elem.attr 'id', 'nx-view-menu-container'
$scope.$root.$on '$stateChangeSuccess', ->
elem.empty()
]
This is implemented like so:
<div class="navbar navbar-submenu2">
<ul class="nav navbar-nav navbar-submenu2">
<!-- container for dynamic view-specific menu content -->
<li nx-view-menu-container class="nav navbar-nav">
</li>
<!-- static nav items -->
<li class="nav navbar-nav" ng-repeat="nav in app.subnav">
<a><span class="current-del-name">{{nav.label}}</span></a>
</li>
nxViewMenu
This directive is declared on each view and serves as the view-specific elements container that gets moved to the directive above.
.directive 'nxViewMenu', [
()->
dir =
restrict: 'EAC'
link: ($scope, elem, attrs)->
elem.ready ->
name = attrs.nxViewMenu or '#nx-view-menu-container'
target = $(name)
if !target.length
return
target.empty()
target.append elem
]
And inside each view where I might want a dynamic menu to appear in else where (in this case, in the app-level's nav container) I declare it inside my view templates.
view 1
<div class="nx-view-menu">
<a class="btn btn-default">
<i class="fa fa-lg fa-bars nx-clickout-filter"></i>
<!--<label name="panel-title" style="color:#58585b; padding-left:5px;" class="nx-clickout-filter">Quick Links</label>-->
</a>
</div>
view 2
<div class="nx-view-menu">
<input typeahead="...">
</div>
view 3
<div class="nx-view-menu">
<date-picker>...</date-picker>
</div>
closing arguments
Firstly you are doing things declaratively so it's easy to follow the logic once you understand the directives.
It keeps the top level controllers ignorant of the specific views' logic while still displaying the DOM in a consistent way
It bypasses the need for special services or some hacky message bus
It simplifies your overall DOM structure because you don't need to set up some dual-view containers (in my case that was the other option in using ui.router) or parallel states.
Can I have in directive same thing as in lisp macro, where I can access source html inside a element and based on it's html create new html that will be injected into the DOM?
And I get access to the html inside and create new template based on that html.
Is html -> html transformation is possible in Angular? From what I read it's not, but maybe there is some advanced use of the framework that will allow for this.
For instance I have code like this:
<directive>
<ul>
<li>Foo</li>
<li>Bar</li>
</ul>
</directive>
and what output:
<directive>
<ul>
<li ng-hide="some-logic">Foo</li>
<li ng-hide="some-other-logic">Bar</li>
</ul>
</directive>
So what I need is template function that will get html as argument and return template that will have angular code.
template: function(source_xml) {
return source_xml.find('li').each(function() {
$(this).attr('ng-hide', '<SOME LOGIC>');
});
}
There is a way to use $compile inside transclude function, but I'm not quite sure how to use it and if it will work as think.
I have a <ul> that gets populated with the server. But in that controller there is also an iframe. When the <li>'s arrive there is some disconnect between them and the iframe even though they are in the same controller.
When you click one of the li's it should change the class on the iframe but it's not. However, If I move the iframe inside of the ng-repeat that injects the iframe it works.
View
<div class="content" ng-controller="FeedListCtrl">
<ul>
<li ng-repeat="item in items">
<div data-link="{{item.link}}" ng-click="articleShowHide='fade-in'">
<div ng-bind-html="item.title" style="font-weight:bold;"></div>
<div ng-bind-html="item.description"></div>
<!-- it works if i put the iframe here -->
</div>
</li>
</ul>
<!-- doesn't work when the iframe is here -->
<iframe id="article" ng-class="articleShowHide" src=""></iframe>
</div>
Here is the controller. It does an ajax call to get the data for each <li>
Controller
readerApp.controller('FeedListCtrl', ["$scope", "$http", "FeedListUpdate", function ($scope, $http, FeedListUpdate) {
$scope.setFeed = function (url) {
$http.get('feed?id=' + FeedListUpdate.GetCurrentFeedUrl()).success(function (data) {
$scope.items = data.currentFeed.items;
});
};
}]);
When inside of an ng-repeat you are in a different scope which means you are not setting the variable you think you are. Use $parent and that should work. The syntax is:
<div data-link="{{item.link}}" ng-click="$parent.articleShowHide='fade-in'">
Side note for others finding this - sometimes adding curly brackets helps as well. For more information on ng-class see here: http://docs.angularjs.org/api/ng/directive/ngClass
An Example
In case anyone wants to see this in action, I put together an example demonstrating a few ways to set the class as well as demonstrating the issue in scope (See: http://plnkr.co/edit/8gyZGzESWyi2aCL4mC9A?p=preview). It isn't very pretty but it should be pretty clear what is going on. By the way, the reason that methods work in this example is that the scope doesn't automatically redefine them the way it does variables so it is calling the method in the root scope rather than setting a variable in the repeater scope.
Best of luck!
I'm using snap.js with AngularJS using the angular-snap.js directive.
https://github.com/jtrussell/angular-snap.js
I'm also using Andy Joslin's angular-mobile-nav.
I'm wondering where I should store the code for the menu:
<snap-drawer>
<p>I'm a drawer! Where do I go in the angular code?</p>
</snap-drawer>
Because this isn't a unique page within the angular-mobile-nav, I'm currently putting the on every page and just using a directive that contains all my menu code/html.
Seems like this could be inefficient as it is loading a new directive on each page, right? Any idea on how to do this better?
Thanks!
So this is what I've done (I also use angular-mobile-nav and angular-snap.js).
This is my HTML Code
<body ng-app="MyApp">
<div snap-drawer>
<ul class="list">
<li ng-repeat="item in sidebar.items" ng-i18next="{{item.name}}" ng-tap="snapper.close();go(item.link)"></li>
</ul>
</div>
<div id="container" snap-content snap-options="snapOpts">
<div mobile-view=""></div>
</div>
</body>
please note that go() is the function to change the page and that I'm using ng-i18next to translate my items. Also ng-tap is a directive which listens for touch events instead of mouse events. With Angular >1.1.5 there's a mobile module, so my ng-tap directive won't be needed anymore.
And by using $rootScope I can put items in the sidebar:
$rootScope.sidebar = {
items: [
{
name: 'item_one',
link: 'page_one'
},
...
]
};
So if you want to change the items in the sidebar, simply override $rootScope.sidebar (not $scope.sidebar) in your controller ;)
If you don't like two animations happen at the same time, you could write a function, which waits for the sidebar to close and then change the page. It could look like this:
$rootScope.waitThenGoTo = function (page, time) {
time = time || 200;
$timeout(function () {
$navigate.go(page);
}, time);
};
If you have still question, please comment. I'll try to update this answer as soon as possible.