I'm making an in game UI using awesomium, at some points the game loads up and executes a chunk of javascript which is meant to create arbitrary new UI elements. e.g.
jQuery(document.body).append('<span class="game-status-alert">You Lose!</span>');
That works nicely, the problem comes when I want to create some slightly more advanced UI elements, specifically using angular. For example something like:
function ChatBoxControl($scope) { /* Stuff */ }
jQuery(document.body).append(
'<div ng-controller="ChatBoxControl"><div ng-repeat="line in chat"><span>{{line}}</span></div></div>'
);
Not surprisingly, this does not create a new angular view. It simply adds that html to the document and never binds to the ChatBoxControl.
How can I achieve what I'm trying to do here?
You should $compile dynamically added angular content.
Something like:
jQuery(document.body).append(
$compile(
'<div ng-controller="ChatBoxControl"><div ng-repeat="line in chat"><span>{{line}}</span></div></div>'
)(scope)
);
scope for any element you can get using something like:
var scope = angular.element('#dynamicContent').scope();
Also you should get $compile that can be injected in other controller.
See also: AngularJS + JQuery : How to get dynamic content working in angularjs
You might want to use ng-include combined with ng-repeat.
Here is an simple example: http://plunker.no.de/edit/IxB3wO?live=preview
<div ng-repeat="dom in domList" ng-include="dom"></div>
Parent $scope will keep the list of partials loaded into the view.
And ng-repeat + ng-include will iterate over and display partials according
to the list.
When it is the right timing, you can append the partial into the dom list. e.g.
$scope.domList.push("chatbox.html");
(BTW, putting DOM manipulation into controller is not the angular way.)
Related
I have a template which is nested inside another template which I want to load when i click on a button.
So the nested template is loaded dynamically. This is what I have done so far.
This is the main body.html (this loads when a url is provided in the browser e.g. http://url#/newtemplate)
<div ui-view> </div>
Other section of the code has been removed for brevity
This is the new_template.html which I expects it to show when I click a button.
When I put a template name directly like below i.e. when I hard code it
<div ui-view="number1"></div>
It loads the template fully.
This is the dynamic model
<button ng-model="template_name" ng-value="number1">Button1</button>
<div ui-view="{{template_name}}"></div>
{{template_name}}
The above does not load the template as I expected. but it shows the string number1 when
the button is clicked
What can I do for it to load the template....
This is my controller
.state('parent',{
url: '/newtemplate',
views:{
'':{
templateUrl: "parent.tpl",
contoller:"controller",
},
'number1#parent':{
templateUrl:"number1.tpl",
contoller:"formcontroller"
},
'number2#parent':{
templateUrl:"number2.tpl",
contoller:"formcontroller"
},
'number3#parent':{
templateUrl:"number3.tpl",
contoller:"formcontroller"
}
}
})
Strange enough when I used the dot notation it did not work so I have to use the absolute naming method.
I also noticed that when I added the nested views as shown above the time it takes before the template gets loaded take a very long time.
Please I would appreciate any help which can allow me to load a nested view at runtime (possibly very fast)
Expecting more answer
I still hope that the I can make use of ui-view/ui-router because of the ability to make use of controller.
I'm not sure you can use uiView to load html dynamically.
I would try another possible solutions:
Use directives
Using ngInclude
I'll leave you an example with ngInclude: https://next.plnkr.co/edit/M5hl71mXdAGth2TE?open=lib%2Fscript.js&deferRun=1&preview
I am trying to get a popover show some html content (it could be angularjs compiled content) and when I click on my "View" link, I do not see my custom directive getting processed and showing correctly (all other content is rendering okay including one that has an ng-repeat inside it suggesting that angularjs saw my angular content right)
http://plnkr.co/edit/Jy8Qlp1rsghwj4NNF2Dn?p=preview
<div ng-controller="ChordCtrl">
<chord-layout chord-matrix="{{matrix}}">
<div id="chordLayoutHolder"></div>
</chord-layout>
</div>
There is a lot going on in this plunkr but the bottom line is when I click on any of the "View" inside the table, I expect dynamic contents of report1.html to show up in my tooltip - Clearly all the other contents show up but it somehow failed to do my complex chord layout rendering - The chord layout diagram rendering is tested independently in another plunkr - http://plnkr.co/edit/q5DDdKHs11OuW6SfLtTG?p=preview
Any help in determining why my chord layout chart is not rendering would be helpful.
Regards
You're having issues because you're using id for your chordLayoutHolder. To be honest, I'm not sure why this is an issue. Perhaps Angular pre-compiles the template and clones it, such that the id is no longer unique. I would be curious to know.
In any case, remove <div id="chordLayoutHolder"></div> and just append to the directive element:
<chord-layout chord-matrix="{{matrix}}">
</chord-layout>
Then, in your directive, change the following lines (that refer to chordLayoutHolder):
$("#chordLayoutHolder").empty();
var svg = d3.select("#chordLayoutHolder")
...
to:
element.empty();
var domElement = angular.element(element)[0];
var svg = d3.select(domElement)
...
to append to the element itself, rather than another placeholder.
Then it would work. Here's the plunker.
I am relatively new to AngularJS.
I have a series of DIVs in a partial view. Each of the DIVs has a unique ID. I want to show / hide these DIVs based on a scope value (that matches one of the unique ID).
I can successfully write out the scope value in the view using something like {{showdivwithid}}
What would be the cleanest way to hide all the sibling divs that dont have an ID of {{showdivwithid}}
I think you are approaching the problem with a jQuery mindset.
Easiest solution is to not use the id of each div and use ngIf.
<div ng-if="showdivwithid==='firstDiv'">content here</div>
<div ng-if="showdivwithid==='secondDiv'">content here</div>
<div ng-if="showdivwithid==='thirdDiv'">content here</div>
If you don't mind the other elements to appear in the DOM, you can replace ng-if with ng-show.
Alternatively use a little directive like this:
app.directive("keepIfId", function(){
return {
restrict: 'A',
transclude: true,
scope: {},
template: '<div ng-transclude></div>',
link: function (scope, element, atts) {
if(atts.id != atts.keepIfId){
element.remove();
}
}
};
});
HTML
<div id="el1" keep-if-id="{{showdivwithid}}">content here</div>
<div id="el2" keep-if-id="{{showdivwithid}}">content here</div>
<div id="el3" keep-if-id="{{showdivwithid}}">content here</div>
First, I want to echo #david004's answer, this is almost certainly not the correct way to solve an AngularJS problem. You can think of it this way: you are trying to make decisions on what to show based on something in the view (the id of an element), rather than the model, as Angular encourages as an MVC framework.
However, if you disagree and believe you have a legitimate use case for this functionality, then there is a way to do this that will work even if you change the id that you wish to view. The limitation with #david004's approach is that unless showdivwithid is set by the time the directive's link function runs, it won't work. And if the property on the scope changes later, the DOM will not update at all correctly.
So here is a similar but different directive approach that will give you conditional hiding of an element based on its id, and will update if the keep-if-id attribute value changes:
app.directive("keepIfId", function(){
return {
restrict: 'A',
transclude: true,
scope: {
keepIfId: '#'
},
template: '<div ng-transclude ng-if="idMatches"></div>',
link: function (scope, element, atts) {
scope.idMatches = false;
scope.$watch('keepIfId', function (id) {
scope.idMatches = atts.id === id;
});
}
};
});
Here is the Plunkr to see it in action.
Update: Why your directives aren't working
As mentioned in the comments on #david004's answer, you are definitely doing things in the wrong way (for AngularJS) by trying to create your article markup in blog.js using jQuery. You should instead be querying for the XML data in BlogController and populating a property on the scope with the results (in JSON/JS format) as an array. Then you use ng-repeat in your markup to repeat the markup for each item in the array.
However, if you must just "get it working", and with full knowledge that you are doing a hacky thing, and that the people who have to maintain your code may hate you for it, then know the following: AngularJS directives do not work until the markup is compiled (using the $compile service).
Compilation happens automatically for you if you use AngularJS the expected, correct way. For example, when using ng-view, after it loads the HTML for the view, it compiles it.
But since you are going "behind Angular's back" and adding DOM without telling it, it has no idea it needs to compile your new markup.
However, you can tell it to do so in your jQuery code (again, if you must).
First, get a reference to the $compile service from the AngularJS dependency injector, $injector:
var $compile = angular.element(document.body).injector().get('$compile');
Next, get the correct scope for the place in the DOM where you are adding these nodes:
var scope = angular.element('.blog-main').scope();
Finally, call $compile for each item, passing in the item markup and the scope:
var compiledNode = $compile(itm)(scope);
This gives you back a compiled node that you should be able to insert into the DOM correctly:
$('.blog-main').append(compiledNode);
Note: I am not 100% sure you can compile before inserting into the DOM like this.
So your final $.each() in blog.js should be something like:
var $compile = angular.element(document.body).injector().get('$compile'),
scope = angular.element('.blog-main').scope();
$.each(items, function(idx, itm) {
var compiledNode = $compile(itm)(scope);
$('.blog-main').append(compiledNode);
compiledNode.readmore();
});
I am working on creating an Angular service that will append a simple notification box to the DOM and display it, without having to add HTML code and write the logic to hide and show it when necessary.
The service is called $notify and is used as below:
$notify.error( "this is an error", {position: "bottom-left"} );
The service will use angular.element to build the notification box and add it to the DOM. All of this works great. However, I am also using ngAnimate and animate.css to have the notification smoothly slide in on show and slide out upon closing. I've verified that the animations work if I simply paste the notification HTML code into my page but will not work when the code is added dynamically via the service. Do items have to be in the DOM at document load for ngAnimate to work? I've verified that the Angular service is loaded and properly inserting the HTML code but no animations are being applied. Here's what the HTML and CSS look like.
HTML:
<div class="simple-notify simple-notify-top-left simple-notify-info" ng-if="toggle">
<simple-notify-header>Hello!<span class='simple-notify-dismiss pull-right' ng-click='doSomething()'>×</span></simple-notify-header>
<simple-notify-body>Some bogus text here!</simple-notify-body>
</div>
CSS/LESS:
.simple-notify {
&.ng-enter {
display:none;
animation: #animate-enter #animation-length;
-webkit-animation: #animate-enter #animation-length;
}
&.ng-enter-active {
display:block;
}
&.ng-leave {
animation: #animate-leave #animation-length;
-webkit-animation: #animate-leave #animation-length;
}
}
Thanks!!!
You should never modify elements on the page from a service, this is what a directive is for. You should create an attribute directive on your body HTML element and in that place your logic. You can use $rootScope.$broadcast from your $notify service and $scope.$on in your directive. Alternatively, you can scrap the $notify service altogether and just use $rootScope.$broadcast as it can accept as many arguments as you want.
Lastly, you'll need to use the $compile service to make your HTML run properly after you've added it to the body. The $compile is what turns raw DOM into code Angular will process.
From inside your directive's scope.$on handler in the link function, you'd have something like.
$compile('<div>template code here</div>')(scope, function(cloned, scope){
element.append(cloned);
});
But you'd also need a cleanup as well. You might just add the code once as the directive's template and show/hide it with different content instead of adding/removing the DOM.
I have a controller like this:
#VariantModalCtrl = ($scope) ->
$scope.upload_variant_image = ->
alert("test")
When I try to call upload_variant_image function using ng-click, it only works when binding to a static DOM (when the DOM loads), I have a link like this:
<%= link_to "test", "" , "ng-click" => "upload_variant_image()" %>
but this element is dynamically added after the DOM is loaded, so ng-click doesn't work.
Update
Just found part of my answer using $compile function:
AngularJS + JQuery : How to get dynamic content working in angularjs
BUT it doesn’t work when I update the DOM like this in Rails:
$(".modal-body").html($compile("<%= j render("/variants/form", :variant => #variant) %>")(scope));
I would warn you that you may not be fully embracing Angular philosophy if you're manipulating the DOM through Angular-external means. Adding links dynamically with AngularJS is as simple as anything else and will probably be much easier and more idiomatic than getting your external library to play nice. Here is a simple example based on your question:
<ul>
<li ng-repeat="item in items">
<a ng-click="upload_variant_image()">{{item.name}}</a>
</li>
</ul>
All you need to do is wire up the scope data appropriately and this will always "just work."
That said, if you are manipulating the DOM, you can cause AngularJS bindings to occur (to, say, ng-click as you desire) by using $compile service. Do consider the above better option, though. :)