What's the best way to append an element using angular? - javascript

My objective is to show a grid of products and ads between them.
warehouse.query({limit: limit, skip: skip}).$promise
.then(function(data) {
for (var i = 0; i < data.length; i++) {
var auxDate = new Date(data[i].date);
data[i].date = auxDate.toISOString();
}
Array.prototype.push.apply($scope.products, data);
//add an img ad
var warehouseElem = angular.element(document.getElementsByClassName('warehouse')[0]);
var newAd = $sce.trustAsHtml('<img src="/ad/?r=' + Math.floor(Math.random()*1000) + '"/>');
warehouseElem.append(newAd);
skip += 9
});
Doesn't work.
I already tried simply using pure javascript like,
var warehouseElem = document.getElementsByClassName('warehouse')[0];
var newAd = document.createElement('img');
warehouseElem.appendChild(newAd);
Also doesn't work.
I suppose I need to do something with angular, can't find out what. I think it's sanitize but maybe I just don't know how to use it.
Remember I need to inject an img every once in a while between products.

This is a job for ng-repeat!
<div ng-repeat="data in datas">
<div>[show data here]</div>
<img src="/ad/?r=' + Math.floor(Math.random()*1000) + '"/>
</div>
If you have bind your "datas" in scope and Math too like this in your controller like this it should works
$scope.datas // this is your list of products
$scope.Math = Math;
If you don't want to spam add for each line you can use ng-if with $index like this :
<div ng-if="$index%2==0">
<img src="/ad/?r=' + Math.floor(Math.random()*1000) + '"/>
</div>
This will make it display add every 2 lines.
Since you seemed to come from a jQuery-like (or native DOM manipulation) background, I suggest you to read that post : "Thinking in AngularJS" if I have a jQuery background?.
This will explain you why in angular you almost don't manipulate DOM and quite some other things (only in directives).
EDIT : to fix the grid problem, just merging my two html block build your array of datas like this :
$scope.myArray = [product[0], ad[0] or just an empty string it will work still, product[1], ad[1]]
And the html
<div ng-repeat="data in datas">
<div ng-if="$index%2==0">[show data here]</div>
<img ng-if="$index%2==1 src="/ad/?r=' + Math.floor(Math.random()*1000) + '"/>
</div>

In AngularJS you should generally avoid doing DOM manipulation directly and rather rely on angular directives like ng-show/ng-hide and ng-if to dynamically hide sections of a template according to the specific case.
Now back to the problem at hand.
Assuming that you are trying to render a list of products loaded with the code displayed above and display an ad for some of them, you can try the following.
<!-- place the img element in your template instead of appending -->
<div ng-repeat="product in products">
<!-- complex product template-->
<!-- use ng-if to control which products should have an ad -->
<img ng-src="product.adUrl" ng-if="product.adUrl" />
</div>
Then in your controller set adUrl for products that should have an ad displayed.
warehouse.query({limit: limit, skip: skip}).$promise
.then(function(data) {
for (var i = 0; i < data.length; i++) {
var hasAd = // set to true if this product should have an add or not
var auxDate = new Date(data[i].date);
data[i].date = auxDate.toISOString();
if(hasAd){
data.adUrl = "/ad/?r=" + Math.floor(Math.random()*1000);
}
}
Array.prototype.push.apply($scope.products, data);
skip += 9
});
I am most probably assuming too much. If that is the case please provide more details for your specific case.

If you declare a scope variable,
$scope.newAd = $sce.trustAsHtml('<img src="/ad/?r=' + Math.floor(Math.random()*1000) + '"/>');
and in your HTML template, have a binding like
<div ng-bind-html="newAd"></div>,
it should work.

Related

Javascript - make a list with bootstrap media objects

What would be the best way to create a list with bootstrap media objects with javascript.
<div class="media">
<div class="media-left">
<a href="#">
<img class="media-object" src="..." alt="...">
</a>
</div>
<div class="media-body">
<h4 class="media-heading">Media heading</h4>
...
</div>
</div>
I am fetching json data from an api endpoint and was thinking of making something like in this example, or using maybe some templating engine. So, I wonder how would I do this here and if it would look messy with creating all the elements necessary for bootstrap media object component?
function makeUL(array) {
// Create the list element:
var list = document.createElement('ul');
for(var i = 0; i < array.length; i++) {
// Create the list item:
var item = document.createElement('li');
// Set its contents:
item.appendChild(document.createTextNode(array[i]));
// Add it to the list:
list.appendChild(item);
}
// Finally, return the constructed list:
return list;
}
First of all, you need to clarify how you are planning to handle the data inside media object. What i mean is,
Are you going to get the data from a database or a JSON file or sth?
Which proggramming language are you using? PHP, ASP, Python... or None
Do you have to do this with JavaScript, because it can be achieved with any other languages
In order to create an array of something, you should define that something first.
Here is a starting point for you:
First create an empty div with an id eg: #yourContainerID
Then create a new js file, and don't forget to give referance to that file.
And put these code inside that JS file
$( document ).ready(function() {
$.ajax({
type : 'post',
url: 'yourFile.json', //Here you will fetch records
dataType: 'json',
success: function(data) {
var mediaData; // Here you create data to put into container
mediaData = '';
for(var i = 0; i < data.length; i++){
mediaData += '<div class="media">';
mediaData += '<div class="media-left">';
...
mediaData += data.yourData // Your data
...
mediaData += '</div>' // You close divs
}
$('#yourContainerID').html(mediaData);
}
});
});
Since I don't have a clue on your data structure, this is just for demo. You have to edit the code for your needs.

How to use Functional $scope methods in HTML templates without using ng-click?

I am learning angular for my next project, I am confused how to use a $scope variable (call the function) in my template file, all the example I could find use it with ng-click, or more general how to access business logic of the controller in templates views? for example I have this function
$scope.rows = function(){
var rowNumber;
rowNumber = Math.floor($scope.itemNumber / 6);
var rows = [];
for(var i = 0; i < rowNumber; i++){
rows.push('row'+ i);
console.log(rows);
}
return rows;
};
I want to access this rows array which is getting dynamically generated in ng-repeat?
its easy, just use
<div ng-repeat="row in rows()">
<span>{{ row }} </span>
</div>
here is more information about ng-repeat https://docs.angularjs.org/api/ng/directive/ngRepeat
You do not want to mix your business logic with your markup. But if you want to perform some logic and use the result in your view to conditionally render some element, you can add a method to your scope and call that
$scope.isButtonVisible()
{
return $scope.someProperty==$scopeanotherProperty;
}
now you can use it in your view
<button ng-show="isButtonVisible" >Save</button>
EDIT : As per the comment, If you want to use an array, simply do this
$scope.products=[];
for(var i=0;i<5;i++){
$scope.products( { name : 'row'+ i} );
}
now in your view,
<div ng-repeat="product in products>
<h1>{{product.name}}
</div>

Performance very slow, javascript, mustache rendering templates in a loop

I'm having terrible performance problems using .append and mustache, as you can see i'm looping over events and rendering a template that i am appending in to the DOM - can anyone suggest how make this a lot more efficient, currently the app grinds to a halt in Chrome..
Anyhelp will be be very much welcome, this is a major problem for me at the moment.
_.each(currentEvents.events, function(list, index) {
var template = "{{#.}}<div data-start-time='{{start_time}}' data-end-time='{{end_time}}' data-event-id='{{event_id}}' data-event-location-id='{{event_location_id}}' data-type-id='{{event_type_id}}' class='venue-event default-event event-type-{{event_type_id}}'>\
<div class='event-inner clearfix'>\
<div class='venue-event-type'>{{event_type}}</div>\
<div class='venue-event-time'>{{start_time}} - {{end_time}}</div>\
<div class='venue-event-title'>{{event_title}}</div>\
<div class='venue-event-sponsor'>{{sponsor}}</div>\
</div>\
</div>{{/.}}"
var eventOutput = Mustache.render(template, list);
$('[data-event-location-id=' + list.event_location_id + ']').append(eventOutput);
});
The var template should be init outside the loop (only once!):
var i, eventLength=currentEvents.events;
var template = "{{#.}}<div data-start-time='{{start_time}}' data-end-time='{{end_time}}' data-event-id='{{event_id}}' data-event-location-id='{{event_location_id}}' data-type-id='{{event_type_id}}' class='venue-event default-event event-type-{{event_type_id}}'>\
<div class='event-inner clearfix'>\
<div class='venue-event-type'>{{event_type}}</div>\
<div class='venue-event-time'>{{start_time}} - {{end_time}}</div>\
<div class='venue-event-title'>{{event_title}}</div>\
<div class='venue-event-sponsor'>{{sponsor}}</div>\
</div>\
</div>{{/.}}";
This mustache parse method helps speeds up future uses of render method (documentation):
Mustache.parse(template);
using _.each or forEach it more expensive than the native for loop (pay attention that I didn't use the currentEvents.events.length expression in the for condition - cause we prefer it to be calculated only once):
for (i = 0 ; i < eventLength ; i++ ) {
outPutArray.push(Mustache.render(template, list));
});
For this line to work you should change your flow design, from this jquery selector i can see your ids are already has been appended inside the DOM, and now you only go and append to each one o them the relevent HTML, this is wrong, you should have do the appending process to the ids which we can not see in your code and make it work togheder with the appending of the additional markup. like:
$('#parent-div').append(outPutArray.join(" "));
All code together:
var i, eventLength=currentEvents.events;
var template = "{{#.}}<div data-start-time='{{start_time}}' data-end-time='{{end_time}}' data-event-id='{{event_id}}' data-event-location-id='{{event_location_id}}' data-type-id='{{event_type_id}}' class='venue-event default-event event-type-{{event_type_id}}'>\
<div class='event-inner clearfix'>\
<div class='venue-event-type'>{{event_type}}</div>\
<div class='venue-event-time'>{{start_time}} - {{end_time}}</div>\
<div class='venue-event-title'>{{event_title}}</div>\
<div class='venue-event-sponsor'>{{sponsor}}</div>\
</div>\
</div>{{/.}}";
Mustache.parse(template);
for (i = 0 ; i < eventLength ; i++ ) {
outPutArray.push(Mustache.render(template, list));
});
$('#parent-div').append(outPutArray.join(" "));

Update class on one progress bar where there are multiple progress bars

I'm having trouble updating the class on bootstrap progress bar.
I have multiple progress bars on a single page and I want to only update the class on one at the time if it goes over for example 20%. From "progress progress-success" to "progress progress-danger"
I have tried many variations, my code looks now like this where I'm trying to set unique id on
every bar by ng-repeat (AngularJS).
Javascript :
$scope.result = ProjectServices.projects().get(function(d) {
$scope.tasktime = []
console.log($scope.result.Tasks);
var tasks = $scope.result.Tasks;
for(var i = 0; i < tasks.length; i++) {
var prosent = ( tasks[i].EstimatedTimeLeft / tasks[i].EstimatedTime ) * 100;
console.log(prosent);
var extra = '#bar' + i;
if(prosent > 20 ) {
$(extra).removeClass("progress progress-success");
$(extra).addClass("progress progress-danger");
}
$scope.tasktime.push(prosent);
}
HTML : note $index is angularjs syntax to generate numbers from 0...?
<div id="bar{{$index}}" class="progress progress-success">
<div class="bar" style="width:{{tasktime[$index] | number:2}}%">{{tasktime[$index] | number:1}}% </div>
</div>
But it dosent seem to pick up the element, because I know it goes into the if statement.
My extra variable var extra = '#bar' + i; dosen't seem to be legit either any idead how to update single progress bar and generate id for the jQuery selector command?
I think in this example you would be better served to use the ng-class attribute and let angular make the class changes instead of forcing jquery into your controller which generally you wouldn't want to do:
http://docs.angularjs.org/api/ng.directive:ngClass
<div id="bar{{$index}}" class="progress " ng-class='{"progress-success":tasktime[$index].prosent<=20,"progress-danger":tasktime[$index].prosent>20}'>
<div class="bar" style="width:{{tasktime[$index] | number:2}}%">{{tasktime[$index] | number:1}}% </div>
</div>
A quick note I realized the prosent isn't defined, however you could just as easily define a function in your controller:
$scope.prosent = function(item) {
return ( item.EstimatedTimeLeft / item.EstimatedTime ) * 100
}
And the html would be: ng-class='{"progress-success":prosent(item) or prosent(tasktime[$index] depending on your repeater/etc.
Lastly, it seems like perhaps you have not shared all of your code.. I would set up an ng-repeat and do it like this:
<div ng-repeat='task in tasktime' class='progress' ng-class='{"progress-success":task.prosent<=20,"progress-danger: task.prosent>20'>
<div class="bar" style="width:{{task | number:2}}%">{{task | number:1}}% </div>
etc

Move Through Object List

<div id="team-name">{{teams[0].name}}</div>
<button id="next">Next</button>
When the "next" button is hit I would like the team-name to be the next team name in the list, i.e. 0 becomes 1?
I have a feeling I need JS to do this - but I am not sure how I would use JS to do this.
Also, the list is generated from the server.
UPDATE
{{ }} is part of a templating system - Jinja2 to be precise.
The teams list is passed into the webpage through Jinja2 - so the webpage has access to the entire teams list - I hope that makes sense.
class Team(db.Model):
__tablename__ = 'Team'
name = db.Column(db.String(21))
matches_total = db.Column(db.Integer())
matches_won = db.Column(db.Integer())
matches_lost = db.Column(db.Integer())
Make a list containing the names available as team_names and update your template like this:
<div id="team-name" data-index="0" data-entries="{{ team_names|tojson }}">{{teams[0].name}}</div>
<button id="next">Next</button>
In case you are using flask which seems to be the case, pass this to your render_template() call:
team_names=[t.name for t in Team.query]
Then you can use the following jQuery snippet to do what you want:
$('#next').on('click', function(e) {
e.preventDefault();
var nameElem = $('#team-name');
var entries = nameElem.data('entries');
var index = (nameElem.data('index') + 1) % entries.length;
nameElem.text(entries[index]).data('index', index);
})
Note: This answer assumes the list is not too big.

Categories