How can I reduce the number of API calls (Google Analytics API) - google-analytics

I'm creating a website-dashboard for internal use at my company. We've a TV-screen in our office and I want to display some stats of our 8 most popular websites. It should contain 8 graphs of last weeks users/sessions per site. I also want to display the active-users for each site.
I've managed to come up with a script and wrote (with some help) a function so I could easily call the different sites by just changing the view id's of the corresponding properties, instead of copying the same code eight times.
The problem I'm facing is a 'Quota Error: User Rate Limit Exceeded.' error in the console, because of the 8 sites. It's working fine with 2 or 3, but 8 seems to be too much. I've already raised the Requests per 100 seconds per user. too 1000.
Is there any way I could reduce the number of API calls by rewrite some of my code? Or is there another way to achieve this dashboard-kind-of-thing?
`
<div class="container-fluid">
<div class="row">
<div id="auth-button"></div>
<div class="col-lg-4 dashitem">
<h1>name</h1>
<figure class="Chartjs-figure" id="-container"></figure>
<ol class="Chartjs-legend" id="legend-1-container"></ol>
<div id="active-users-container1"></div>
</div>
<div class="col-lg-4 dashitem">
<h1>name</h1>
<figure class="Chartjs-figure" id="[name]-container"></figure>
<ol class="Chartjs-legend" id="legend-2-container"></ol>
<div id="active-users-container2"></div>
</div>
<div class="col-lg-4 dashitem">
<h1>name</h1>
<figure class="Chartjs-figure" id="[name]-container"></figure>
<ol class="Chartjs-legend" id="legend-3-container"></ol>
<div id="active-users-container3"></div>
</div>
<div class="col-lg-4 dashitem">
<h1>name</h1>
<figure class="Chartjs-figure" id="[name]-container"></figure>
<ol class="Chartjs-legend" id="legend-4-container"></ol>
<div id="active-users-container4"></div>
</div>
<div class="col-lg-4 dashitem">
<h1>name</h1>
<figure class="Chartjs-figure" id="[name]-container"></figure>
<ol class="Chartjs-legend" id="legend-5-container"></ol>
<div id="active-users-container5"></div>
</div>
<div class="col-lg-4 dashitem">
<h1>name</h1>
<figure class="Chartjs-figure" id="[name]-container"></figure>
<ol class="Chartjs-legend" id="legend-6-container"></ol>
<div id="active-users-container6"></div>
</div>
<div class="col-lg-4 dashitem">
<h1>name</h1>
<figure class="Chartjs-figure" id="[name]-container"></figure>
<ol class="Chartjs-legend" id="legend-7-container"></ol>
<div id="active-users-container7"></div>
</div>
<div class="col-lg-4 dashitem">
<h1>name</h1>
<figure class="Chartjs-figure" id="[name]-container"></figure>
<ol class="Chartjs-legend" id="legend-8-container"></ol>
<div id="active-users-container8"></div>
</div>
</div>
</div>
{literal}
<script>
gapi.analytics.ready(function() {
var CLIENT_ID = '[client-id]';
gapi.analytics.auth.authorize({
container: 'auth-button',
clientid: CLIENT_ID,
userInfoLabel:""
});
gapi.analytics.auth.on('success', function(response) {
//hide the auth-button
document.querySelector("#auth-button").style.display='none';
});
function renderIndividualChart(id, chartId, legendId, activeUsersId) {
// Adjust `now` to experiment with different days, for testing only...
var now = moment(); // .subtract(3, 'day');
var thisWeek = query({
'ids': "ga:" + id,
'dimensions': 'ga:date,ga:nthDay',
'metrics': 'ga:users',
'start-date': moment(now).subtract(1, 'day').day(0).format('YYYY-MM-DD'),
'end-date': moment(now).format('YYYY-MM-DD')
});
var lastWeek = query({
'ids': "ga:" + id,
'dimensions': 'ga:date,ga:nthDay',
'metrics': 'ga:users',
'start-date': moment(now).subtract(1, 'day').day(0).subtract(1, 'week')
.format('YYYY-MM-DD'),
'end-date': moment(now).subtract(1, 'day').day(6).subtract(1, 'week')
.format('YYYY-MM-DD')
});
Promise.all([thisWeek, lastWeek]).then(function(results) {
var data1 = results[0].rows.map(function(row) { return +row[2]; });
var data2 = results[1].rows.map(function(row) { return +row[2]; });
var labels = results[1].rows.map(function(row) { return +row[0]; });
labels = labels.map(function(label) {
return moment(label, 'YYYYMMDD').format('ddd');
});
var data = {
labels : labels,
datasets : [
{
label: 'Vorige Week',
fillColor : 'rgba(220,220,220,0.5)',
strokeColor : 'rgba(220,220,220,1)',
pointColor : 'rgba(220,220,220,1)',
pointStrokeColor : '#fff',
data : data2
},
{
label: 'Deze Week',
fillColor : 'rgba(151,187,205,0.5)',
strokeColor : 'rgba(151,187,205,1)',
pointColor : 'rgba(151,187,205,1)',
pointStrokeColor : '#fff',
data : data1
}
]
};
new Chart(makeCanvas(chartId)).Line(data);
generateLegend(legendId, data.datasets);
});
var activeUsers = new gapi.analytics.ext.ActiveUsers({
ids: "ga:" + id,
container: activeUsersId,
pollingInterval: 5
});
//activeUsers.execute();
}
function query(params) {
return new Promise(function(resolve, reject) {
var data = new gapi.analytics.report.Data({query: params});
data.once('success', function(response) { resolve(response); })
.once('error', function(response) { reject(response); })
.execute();
});
}
function makeCanvas(id) {
var container = document.getElementById(id);
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
container.innerHTML = '';
canvas.width = container.offsetWidth;
canvas.height = container.offsetHeight;
container.appendChild(canvas);
return ctx;
}
function generateLegend(id, items) {
var legend = document.getElementById(id);
legend.innerHTML = items.map(function(item) {
var color = item.color || item.fillColor;
var label = item.label;
return '<li><i style="background:' + color + '"></i>' +
escapeHtml(label) + '</li>';
}).join('');
}
// Set some global Chart.js defaults.
Chart.defaults.global.animationSteps = 60;
Chart.defaults.global.animationEasing = 'easeInOutQuart';
Chart.defaults.global.responsive = true;
Chart.defaults.global.maintainAspectRatio = false;
function escapeHtml(str) {
var div = document.createElement('div');
div.appendChild(document.createTextNode(str));
return div.innerHTML;
}
renderIndividualChart(xxx9412, '[name]-container', 'legend-1-container', 'active-users-container1');
renderIndividualChart(xxx11510, '[name]-container', 'legend-2-container', 'active-users-container2');
renderIndividualChart(xxx8011, '[name]-container', 'legend-3-container', 'active-users-container3');
renderIndividualChart(xxx4711, '[name]-container', 'legend-4-container', 'active-users-container4');
renderIndividualChart(xxx4009, '[name]-container', 'legend-5-container', 'active-users-container5');
renderIndividualChart(xxx61037, '[name]-container', 'legend-6-container', 'active-users-container6');
renderIndividualChart(xxx0969, '[name]-container', 'legend-7-container', 'active-users-container7');
renderIndividualChart(xxx5362, '[name]-container', 'legend-8-container', 'active-users-container8');
});
</script>
`

I'm guessing you are exceeding the 10QPS per IP address limit. Since you are sending about 16 (8 * 2) calls all at once, see General Quota limits.
Be aware that there are also limits on the number of calls per day (50,000) and per Analytics View (10,000) and limits on the number of concurrent requests per view (10).
I think the easiest thing to do is to chain the calls one after the other instead of firing all the calls all at once. Depending on how fast the RPC is you might still run into the qps limits. The better thing to do is to add rate limiting on the GA data calls and also add some retry logic with exponential backoff on quota error.

Related

Vue v-if not reading variable

I am using Vue 2 on Laravel 5.3 (using bxslider for carousel)
The following code gives me a single image (img1) slider.
If I remove v-ifs it does give me 3 image (with img1 img2 img3 in the slider).
Does productChoiceSelected.img2 returns true if it is not null from the database?
EDIT (added full code of the component)
<div class="col-md-4">
<ul class="bxslider">
<li>
<img :src="productChoiceSelected.img1">
</li>
<li v-if="productChoiceSelected.img2">
<img :src="productChoiceSelected.img2">
</li>
<li v-if="productChoiceSelected.img3">
<img :src="productChoiceSelected.img3">
</li>
</ul>
</div>
</div>
</template>
<script>
export default {
data(){
return{
product:[],
productChoiceSelected:[],
productChoices:[]
}
},
props:[
'productId'
],
mounted(){
this.getProduct()
this.getAllProductChoices()
},
methods:{
getProduct(){
var vm = this;
vm.$http.get('/getProduct/'+vm.productId).then((response)=>{
vm.product = response.data.data.product;
vm.productChoiceSelected = response.data.data.productChoiceDefault;
});
},
getAllProductChoices(){
var vm = this;
vm.$http.get('/getAllProductChoices/'+vm.productId).then((response)=>{
vm.productChoices = response.data.data.productChoices;
});
}
}
}
</script>
EDIT #2
I guess it is because productChoiceSelected.img2 is a url directory? it is http://localhost:8000/img/products/1-2.bmp
I can figure out few issues in your code:
You need to have img1 and these variables defined as these are to be set reactively to be used in vue templated.
You need to move $('.bxslider').bxSlider({}) code in the updated section as this needs to be executed once you get the data updated from backend.
Following are code changes:
var demo = new Vue({
el: '#demo',
data: function(){
return {
productChoiceSelected: {
img1:'',
img2:'',
img3:''
}
};
},
mounted(){
this.getProduct()
},
updated(){
$('.bxslider').bxSlider({});
},
methods:{
getProduct(){
var vm = this;
setTimeout(function () {
vm.productChoiceSelected.img1 = 'http://bxslider.com/images/730_200/hill_trees.jpg'
// vm.productChoiceSelected.img2 = 'http://bxslider.com/images/730_200/houses.jpg'
vm.productChoiceSelected.img3 = 'http://bxslider.com/images/730_200/hill_fence.jpg'
}, 300)
}
}
})
see updated fiddle.
Thanks for saurabh contribution above, as I am still unsure where the problem is, here is how I solve the problem by myself for you guys' reference.
Change in code:
<template>
<div class="col-md-5">
<div class="col-md-12">
<ul class="bxslider">
<li>
<img :src="productChoiceSelected.img1">
</li>
<li v-if="img2">
<img :src="productChoiceSelected.img2">
</li>
<li v-if="img3">
<img :src="productChoiceSelected.img3">
</li>
</ul>
</div>
</div>
.....
.....
.....
methods:{
getProduct(){
var vm = this;
vm.$http.get('/getProduct/'+vm.productId).then((response)=>{
vm.product = response.data.data.product;
vm.productChoiceSelected = response.data.data.productChoiceDefault;
if(vm.productChoiceSelected.img1){
vm.img1 = true
}
if(vm.productChoiceSelected.img2 != null){
vm.img2 = true
}
if(vm.productChoiceSelected.img3 != null){
vm.img3 = true
}
});
},
......
......
......

How can i re-render a template embedded in another template

currently i'm working on a project for monitoring solar power stations. And i have a panel template that embedded in dashboard template.
and panel template has its own subscribe, but it seems not reactive? how can i make the panel template reactive?? Thanks.
<template name="Dashboard">
<div class="container-fluid">
<div class="row">
{{#if Template.subscriptionsReady}}
{{#each station in stations}}
{{> StationPortlet station=station}}
{{/each}}
{{else}}
Loading...
{{/if}}
</div>
</div>
</template>
And for Dashboard.js
Template.Dashboard.helpers({
stations: function() {
return Stations.find();
}
});
Template.Dashboard.onCreated(function () {
this.subscribe('allStations');
});
code for Station Panel template
<template name="StationPortlet">
<div class="col-md-6 col-sm-12">
{{#if Template.subscriptionsReady}}
{{#with portlet}}
<!--<div class="panel panel-b-accent m-t-sm panel-filled panel-c-success">-->
<div class="panel panel-filled panel-c-{{indicator}}">
<div class="panel-heading" style="color:#f6a821">
{{> panelTools }}
<!-- <a style="display:block" href="{{pathFor '/dashboard/:id' id=this.id.toHexString}}">{{Name}}</a> -->
<h4 style="color:#f6a821">{{Name}}</h4>
</div>
<div class="panel-body">
<div class="row">
<div class="col-md-6 col-xs-6">
<div class="panel panel-filled">
<div class="panel-body">
<h2 class="m-b-none">
{{Capacity}} <span class="slight"> KW</span>
</h2>
<div class="small">設置容量</div>
</div>
</div>
</div>
<div class="col-md-6 col-xs-6">
<div class="panel panel-filled">
<div class="panel-body">
<h2 class="m-b-none" style="color:#f6a821">
{{energyPerKW}} <span class="slight"></span>
</h2>
<div class="small">每 KW 發電度數</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 col-xs-6">
<div class="panel panel-filled">
<div class="panel-body">
<h2 class="m-b-none">
{{EnergyToday}}
<span class="slight"><i class="fa fa-play fa-rotate-270 text-warning"> </i> KWh</span>
</h2>
<div class="small">Energy Today</div>
</div>
</div>
</div>
<div class="col-md-6 col-xs-6">
<div class="panel panel-filled">
<div class="panel-body">
<h3 class="m-b-none">
{{EnergyLifetime}} <span class="slight"><i class="fa fa-play fa-rotate-270 text-warning"> </i> KWh</span>
</h3>
<div class="small">Energy Lifetime</div>
</div>
</div>
</div>
</div>
</div>
<div class="panel-footer">
<div class="slight m-t-sm"><i class="fa fa-clock-o"> </i> Updated: <span class="c-white">{{update}}</span> </div>
</div>
</div>
{{/with}}
{{else}}
<div class="center-block">
{{>LoadingCircle}}
</div>
{{/if}}
</div>
</template>
and for javascript
portlet: function() {
// var station = new ReactiveVar(Template.currentData().station);
var station = this.station;
var entry = {};
entry.id = station._id;
entry.Name = station.Name;
entry.EnergyToday = 0;
entry.EnergyLifetime = 0;
entry.update = null;
entry.Capacity = 0;
entry.indicator = 'success';
//console.log(station);
var inverters = Inverters.find({ Station: station._id, Enabled: true });
var inverter_indicator = 'success';
inverters.forEach(function(inverter) {
entry.Capacity += inverter.Capacity;
//console.log(inverter);
// get the latest logs for this inverter for today
var inverter_logs = InverterLogs.find({ Station: station._id, Inverter: inverter._id }, { sort: { created_at: -1 }, limit: 1 });
inverter_logs.forEach(function(log) {
entry.EnergyToday += log.EnergyToday.value;
entry.EnergyLifetime += log.EnergyLifetime.value;
entry.update = log.created_at;
//console.log(log);
if (log.Inverter_Status.indicator !== 'success') {
console.log('inverter status !=== success');
if (log.Inverter_Status.indicator === 'warning' && inverter_indicator !== 'danger') {
inverter_indicator = 'warning';
}
if (log.Inverter_Status.indicator === 'danger') {
inverter_indicator = 'danger';
}
}
});
});
entry.indicator = inverter_indicator;
// Compare time if latest logs is more than 20 mins or 45 mins. and if yes, set indicator to warning or danger
var twentyMins = 1000*60*20;
var fourtyfiveMins = 1000*60*45;
var latestUpdateIndicator = 'success';
if ((Date.now()-entry.update) > twentyMins) {
// if the date of lastes log is more than 20 mins earlier, set indicator warning
latestUpdateIndicator = 'warning';
if ((Date.now()-entry.update) > fourtyfiveMins) {
latestUpdateIndicator = 'danger';
}
}
if (latestUpdateIndicator !== 'success') {
if (latestUpdateIndicator === 'warning' && entry.indicator === 'success') {
entry.indicator = 'warning';
} else if (latestUpdateIndicator === 'danger' && (entry.indicator === 'success' || entry.indicator === 'warning')) {
entry.indicator = 'danger';
}
}
// if the date of lastes log is more than 20 mins earlier, set indicator warning
//entry.update = moment(entry.update).format('LLL');
entry.update = moment(entry.update).fromNow();
entry.EnergyToday = entry.EnergyToday.toFixed(2);
entry.energyPerKW = (entry.EnergyToday/entry.Capacity).toFixed(2);
entry.EnergyLifetime = entry.EnergyLifetime.toFixed(2);
//entry.update = moment(update).format('LLL');
return entry;
}
Template.StationPortlet.onCreated(function () {
var station = Template.currentData().station;
var self = this;
var now = moment().toDate();
var today = moment(4, "HH").toDate();
Deps.autorun(function() {
self.subscribe('invertersByStation', station._id);
self.subscribe('inverter_logs_ByStation', station._id, today, now, 100);
});
And i'm trying to find good explaination about Blaze render life cycle, if you have any suggestion, plz let me know :)
Here is Publish.js
Meteor.publish('allStations', function () {
var user = Meteor.users.findOne({_id:this.userId});
var client = Clients.findOne({ name: Roles.getGroupsForUser(user)[0] });
if (Roles.userIsInRole(user, 'super-admin', Roles.GLOBAL_GROUP)) {
return Stations.find();
}
else if (Roles.userIsInRole(user, 'client', client.name)) {
console.log('user is client');
var subscribeStations = client.subscribeStations;
return Stations.find({ _id: {$in: subscribeStations} });
} else {
console.log('user is not defined');
return [];
}
});
Meteor.publish('invertersByStation', function (stationID) {
return Inverters.find({ Station: stationID });
});
Meteor.publish('inverter_logs_ByStation', function (stationID, greaterThan, lessThan, limit) {
return InverterLogs.find({ created_at: {$gte: greaterThan, $lte: lessThan}, Station: stationID}, { sort: {created_at: -1}, limit: limit});
});
Your problem come from your helper portlet witch is reactive but don't refer any data reactive source.
What happend?
Just understand helpers natively wrap a Tracker.autorun, witch allow them to rerun when a reactive data change on their scope.
On your helper portlet, you are doing some operations on locally instanciated object entry. However, nothing on your helper is able to trigger reactive rerun.
You should probably look on WHICH CHANGE you want the helper to be rerun.
Once you know that, just use this data as reactive data source (native as Mongo cursor, or wrapped on ReactiveVar).

How can I route my messenger and messages in Meteor?

Im creating a an instant messenger app and im having a little trouble routing it. So, once you go into the app. There is a list of available users. You can click on a user and start chatting. The issue I have is once I click send, the console show an Uncaught TypeError: Cannot read property 'value' of undefined. Im not sure what im doing wrong here. Also I need help show the chat messages sent above. As if you can see the recent and previous messages. Here are my codes. Any examples and helps would be great.
HTML
minstant
<body>
</body>
<!-- this is the main template used by iron:router to build the page -->
<template name="ApplicationLayout">
{{> yield "header"}}
<div class="container">
{{> yield "main"}}
</div>
</template>
<!-- top level template for the nav bar -->
<template name="navbar">
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="/">
Minstant!
</a>
</div>
<div class="nav navbar-nav">
{{> loginButtons}}
</div>
</div>
</nav>
</template>
<!-- Top level template for the lobby page -->
<template name="lobby_page">
{{> available_user_list}}
</template>
<!-- display a list of users -->
<template name="available_user_list">
<h2>Choose someone to chat with:</h2>
<div class="row">
{{#each users}}
{{> available_user}}
{{/each}}
</div>
</template>
<!-- display an individual user -->
<template name="available_user">
<div class="col-md-2">
<div class="user_avatar">
{{#if isMyUser _id}}
<div class="user_avatar">{{getUsername _id}} (YOU)
<br/>
<img src="/{{profile.avatar}}" class="avatar_img">
</div>
{{else}}
<a href="/chat/{{_id}}">
{{getUsername _id}}
<br/>
<img src="/{{profile.avatar}}" class="avatar_img">
</a>
{{/if}}
</div>
</div>
</template>
<!-- Top level template for the chat page -->
<template name="chat_page">
<h2>Type in the box below to send a message!</h2>
<div class="row">
<div class="col-md-12">
<div class="well well-lg">
{{#each recentMessages}}
{{> message}}
{{/each}}
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<form class="new-message">
<input class="input" type="text" name="chat" placeholder="type a message here...">
<button class="btn btn-default">Send</button>
</form>
</div>
</div>
</template>
<!-- simple template that displays a message -->
<template name="message">
<div class = "container">
<div class = "row">
<div class = "username">{{username}}
<img src="/{{profile.avatar}}" class="avatar_img">
</div>
<div class = "message-text"> said: {{messageText}}</div>
</div>
</div>
</template>
Here is my JS
Messages = new Mongo.Collection("messages");
if (Meteor.isClient) {
Meteor.subscribe("messages");
Meteor.subscribe("userStatus");
// set up the main template the the router will use to build pages
Router.configure({
layoutTemplate: 'ApplicationLayout'
});
// specify the top level route, the page users see when they arrive at the site
Router.route('/', function () {
console.log("rendering root /");
this.render("navbar", {to:"header"});
this.render("lobby_page", {to:"main"});
});
// specify a route that allows the current user to chat to another users
Router.route('/chat/:_id', function () {
this.render("navbar", {to:"header"});
this.render("chat_page", {to:"main"});
});
///
// helper functions
///
Template.available_user_list.helpers({
users:function(){
return Meteor.users.find();
}
})
Template.available_user.helpers({
getUsername:function(userId){
user = Meteor.users.findOne({_id:userId});
return user.profile.username;
},
isMyUser:function(userId){
if (userId == Meteor.userId()){
return true;
}
else {
return false;
}
}
})
Template.chat_page.helpers({
recentMessages: function () {
return Messages.find({}, {sort: {createdAt: 1}});
return Meteor.users.find();
},
});
Template.chat_page.events({
// this event fires when the user sends a message on the chat page
'submit .new-message':function(event){
event.preventDefault();
var text= event.target.text.value;
// stop the form from triggering a page reload
event.target.text.value = "";
// see if we can find a chat object in the database
// to which we'll add the message
Meteor.call("SendMessage", text);
},
});
};
Meteor.methods({
sendMessage: function (messageText) {
if (! Meteor.userId()) {
throw new Meteor.Error("not-authorized");
}
Messages.insert({
messageText: messageText,
createdAt: new Date(),
username: Meteor.user().username
});
}
});
// start up script that creates some users for testing
// users have the username 'user1#test.com' .. 'user8#test.com'
// and the password test123
if (Meteor.isServer) {
Meteor.startup(function () {
if (!Meteor.users.findOne()){
for (var i=1;i<9;i++){
var email = "user"+i+"#test.com";
var username = "user"+i;
var avatar = "ava"+i+".png"
console.log("creating a user with password 'test123' and username/ email: "+email);
Meteor.users.insert({profile:{username:username, avatar:avatar}, emails: [{address:email}],services:{ password:{"bcrypt" : "$2a$10$I3erQ084OiyILTv8ybtQ4ON6wusgPbMZ6.P33zzSDei.BbDL.Q4EO"}}});
}
}
},
),
Meteor.publish("messages", function () {
return Messages.find();
});
Meteor.publish("userStatus", function() {
return Meteor.users.find({ "status.online": true });
});
};
The error is with your form submit code. In the console you can see the error is in Template.chat_page.events.submit .new-message on line 73. That will take you right to the error code:
'submit .new-message':function(event){
event.preventDefault();
var text = event.target.text.value;
// stop the form from triggering a page reload
event.target.text.value = "";
}
event.target.text.value doesn't exist. event.target does, but there is no field for text. There is textContent, and you can access the children of the target (which in this case is the form). Put in a console.log(event); and figure out what you are trying to access within the javascript console and then use that to determine what your code should look like. This might work for you:
'submit .new-message':function(event){
event.preventDefault();
var text = event.target.elements.chat.value;
// stop the form from triggering a page reload
event.target.text.value = "";
}
event.target.elements.chat.value comes from the name field of the <input>.

angular-file-upload: additional properties/options to each file in a multi-file upload?

I'm using nervgh's angular-file-upload, https://github.com/nervgh/angular-file-upload/wiki/Module-API.
Is there a way to use the angular-file-upload and allow additional properties to each file when doing a multi-file upload?
I'm using their image sample to start out with: http://nervgh.github.io/pages/angular-file-upload/examples/image-preview/
Trying to add a boolean to each file that the user can set and then I use that on the server side when it's picked up.
You can use formData property shown in Properties section to send to server whatever you need.
formData {Array}: Data to be sent along with the files.
If you're using PHP in server side, I think this post can help you out.
The question is rather old, but as the documentation didn't really help me much, I would like to note down my solution here:
This is how my html looks like (look for "options"):
<div ng-controller="UploadCtrl2" nv-file-drop="" uploader="uploader" filters="customFilter">
<div class="progress progress-xs margin-top-5 margin-bottom-20">
<div class="progress-bar" role="progressbar" ng-style="{ 'width': uploader.progress + '%' }"></div>
</div>
<div class="row">
<div class="col-md-6">
<div ng-show="uploader.isHTML5">
<div class="well my-drop-zone" nv-file-drop="" options="{formData:[{folder:'attachments'}, {recordid:0}]}" uploader="uploader">
Dateien hierher ziehen.
</div>
</div>
</div>
<div class="col-md-6">
<span class="btn btn-primary btn-o btn-file margin-bottom-15"> Dateien auswählen
<input type="file" nv-file-select="" options="{formData:[{folder:'attachments'}, {recordid:0}]}" uploader="uploader" multiple />
</span>
</div>
</div>
</div>
And this is my controller (look for "fileItemOptions"):
app.controller('UploadCtrl2', ['$rootScope', '$scope', 'FileUploader', 'Store',
function ($rootScope, $scope, FileUploader, Store) {
var fileItemOptions = {};
var uploader = $scope.uploader = new FileUploader({
url: $rootScope.app.api.url + '/?c=uploads&a=set&authToken=' + encodeURIComponent(Store.get('X-Xsrf-Token')),
});
// FILTERS
uploader.filters.push({
name: 'customFilter',
fn: function (item/*{File|FileLikeObject}*/, options) {
if(options) fileItemOptions = options;
return this.queue.length < 10;
}
});
uploader.removeAfterUpload = true;
// CALLBACKS
uploader.onAfterAddingFile = function (fileItem, options) {
//console.info('onAfterAddingFile', fileItem);
if(fileItemOptions.formData) {
fileItem.formData = fileItemOptions.formData;
}
};
uploader.onAfterAddingAll = function (addedFileItems) {
setTimeout(function () {
console.log(uploader);
uploader.uploadAll();
}, 500);
};
uploader.onCompleteAll = function () {
$scope.$parent.run.uploadComplete();
fileItemOptions = {}; // cleanup
};
}]);
Whenever a file is added, the custom filter stores the option object in a global variable. The callback "onAfterAddingFile" will read that variable and it to the fileItem object. Quite hacky, but this was the only way I got it running.

Autocomplete generate multiple markers

My goal is to have several input fields with Autocomplete functionality and each input will generate a marker on the map. I then need the map to zoom in to contain all of the markers. I have this working partially, without the Autocomplete functionality. I have tried adding the Google Maps API Places Autocomplete code to my for loop, but to no avail. It gives autocomplete functionality to all the input fields but only places a marker for the last input.
Any help/insight/guidance is appreciated.
Here is my code, the autocomplete is currently deleted since I couldn't get it to work.
<div class="container-fluid">
<div class="row-fluid">
<div class="span4 well">
<div id="locations">
<form>
<label>Location 0</label>
<input id="address0" type="text" size="50" >
<button id="clearField0">Clear</button>
<div class="panel">
<label>Location 1</label>
<input id="address1" type="text" size="50">
<button id="clearField0">Clear</button>
</div>
<div class="panel">
<label>Location 2</label>
<input id="address2" type="text" size="50">
<button id="clearField0">Clear</button>
</div>
</form>
</div>
<button id="addField">+</button>
<button id="deleteField">-</button>
<button id="submit">Submit form</button>
</div>
<div class="span8">
<div id="map-wrapper">
<div id="google-map"></div>
</div>
</div>
</div>
</div>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCtNo_o0u8wYeqgiFF-KpvAtEy18T-PvAo&sensor=false&callback=createMap"></script>
<script type="text/javascript">
var inputFields = parseInt($('form input').size());
var markers = [];
var createMap = function() {
var latlngbounds = new google.maps.LatLngBounds();
var geocoder = new google.maps.Geocoder();
var myOptions = {
center : new google.maps.LatLng(35.9081, -78.8628), //center to RTP
zoom : 12,
zoomControl: true,
mapTypeId: google.maps.MapTypeId.ROADMAP
},
map = new google.maps.Map(document.getElementById("google-map"), myOptions);
$("#submit").on("click", function() {
setAllMap(null);
markers.pop();
for (i=0; i<inputFields; i++) {
var location = $("#address" + i).val();
geocoder.geocode( {'address': location}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
map.setCenter(results[0].geometry.location);
var marker = new google.maps.Marker({
map: map,
position: results[0].geometry.location
});
latlngbounds.extend(results[0].geometry.location);
map.fitBounds(latlngbounds);
} else {
alert("Geocode was not successful for the following reason: " + status);
}
});
};
});
function setAllMap(map) {
for (var i = 0; i < markers.length; i++) {
markers[i].setMap(map);
}
}
$('#addField').click(function(){ //add input field to bottom of form
$('form').append('<div class="panel"><label>Location '+ inputFields +'</label><input id="address'+ inputFields +'" type="text" size="50"><button id="clearField'+ inputFields +'">Clear</button></div>');
inputFields++;
});
$('#deleteField').click(function(){ //delete last input field
$('.panel').last().remove();
inputFields--;
});
};
</script>
My first problem was including the places library in the API reference. Following that, I simply needed two lines of code from Google's Places Autocomplete example page, as opposed to the entire script they offer:
var input = /** #type {HTMLInputElement} */(document.getElementById('searchTextField'));
var autocomplete = new google.maps.places.Autocomplete(input);
These two lines in my for loop solved the problem. Thanks again to Dr. Molle whose comment made me rethink what code I needed.

Resources