Changing image dimensions in the Controller with ng-file-upload - javascript

I have been trying to use the ng-file-upload module for sometime now and have met with considerable success.
My current requirement is to store 2 versions of the uploaded image, one the original and the other a thumbnail. My current code can store the thumbnail or the original but I can't find a way to do them both from the html. Is there any way to do this from the controller?
HTML:
<div class="row">
<div class="small-6 medium-4 large-4 columns">
<button class="button" type="file" ng-model="myActionsCtrl.image" ngf-select="myActionsCtrl.browseImage($file, $invalidFiles)" accept="image/*" ngf-max-size="2MB">Browse</button>
</div>
<div class="small-6 medium-4 large-4 columns">
<button class="button" ng-click="myActionsCtrl.uploadImage()">Upload</button>
</div>
</div>
My Controller:
module.exports = function ($scope, Upload, AuthService) {
let vm = this;
vm.user = AuthService.user
vm.uploadImage = function () {
if (vm.file) {
vm.file.upload = Upload.upload({
url: '/uploads/gallery',
method: 'POST',
file: vm.file
});
vm.file.upload.then(function (response) {
$timeout(function () {
vm.file.result = response.data;
});
}, function (response) {
if (response.status > 0) { }
// $scope.errorMsg = response.status + ': ' + response.data;
}, function (evt) {
vm.file.progress = Math.min(100, parseInt(100.0 *
evt.loaded / evt.total));
});
}
}
vm.browseImage = function (file, errFiles) {
$scope.file = file;
vm.file = file;
$scope.errFile = errFiles && errFiles[0];
}
}

Related

Uploading multiple files and displaying progress bar

I'm working on a project (in Django) where I have created a page to add data information about a file and then add the file itself.
When 'More datasets' button is clicked, it adds another field to upload another file.
This can be done to attach as many files as the end-user wants in one go.
What I need is to upload all the attached files once 'Upload data-sets' is clicked and individual progress bar should be displayed.
So far, I have run through multiple tutorials but came kinda close using Vitor Freitas's tutorial.
JS code :
$(function(){
/*$("#add_new_dataset").click(function(){
$(".file").click();
});*/
$(".file").fileupload({
dataType: 'json',
sequentialUploads: true, /* Send the files one by one */
start: function(e){ /* When the upload process starts, show the modal */
$("#modal-progress").modal("show");
},
stop: function(e){ /* When the upload progress finalizes, hide the modal */
$("#modal-progress").modal("hide");
},
progressall: function(e, data){ /* Update the progress bar */
var progress = parseInt(data.loaded / data.total * 100, 10),
strProgress = progress + "%";
$(".progress-bar").css({"width": strProgress});
$(".progress-bar").text(strProgress);
},
done: function(e, data){
if(data.result.is_valid){
$("#gallery tbody").prepend(
"<tr><td><a href='" + data.result.url + "'>" + data.result.name + "</a></td></tr>"
);
}
}
})
});
Template code :
<form id="form" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div class="container-fluid" id="datasets" style="margin: 0px; padding: 0px;">
<div class="row" onfocusin="populateFilename(this);">
<label class="col-md-2">User Name :</label>
<input class="col-md-2" type="text" id="username" name="user_name" value="{{ user.username }}" readonly />
<label class="col-md-2">Data-set :</label>
<input class="col-md-2" type="text" placeholder="Enter dataset" name="dataset" required />
<label class="col-md-2">Creation Date :</label>
<input class="col-md-2" type="date" placeholder="YYYY-MM-DD" name="creation_date" required />
<label class="col-md-2">Beam Line:</label>
<input class="col-md-2" type="text" placeholder="Enter beam line" name="beamline" required />
<label class="col-md-2">Data-set file:</label>
<input class="col-md-2 file" type="file" name="file" data-url="{% url 'add_data_sets' %}" data-form-data='{"csrfmiddlewaretoken": "{{ csrf_token }}"}' required />
<label class="filename"></label>
<div class="modal fade" id="modal-progress" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Uploading...</h4>
</div>
<div class="modal-body">
<div class="progress">
<div class="progress-bar" role="progressbar" style="width: 0%;">0%</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div style="align: center; margin-top: 5px;">
<div class="container btn-group btn-group-justified btn-group-lg">
<a onmouseup="addRow();" class="btn btn-outline-secondary" style="width: 50%;">More Datasets</a>
<button type="submit" class="btn btn-outline-primary" name="add_new_dataset" id="add_new_dataset">Submit Data-set</button>
</div>
</div>
What should I do? I'm not very good at AJAX but I can't see any code that works to send the data to the server side. Is it so or am I missing something? Kindly ignore my ignorance on the topic and thanks to all in advance.
EDIT :
The JS code has been rewritten based on some of the answers.
document.getElementById('add_new_dataset').onclick = function() {
$(this).preventDefault();
console.log('Files uploading begin');
form_data = new FormData();
const files = document.getElementsByName('file');
let count = 0;
for(let i = 0; i < files.length; i++){
count++;
form_data.append("file", files[i]);
}
$.ajax({
url: "/add_data_sets",
dataType: 'json',
contentType: false,
processData: false,
data: form_data,
type: 'POST',
success: function(files, response, xhr, pd){
$('.file').show();
if(files.status != false){
$('.progress-bar').val('/location/' + files.filename);
var fileData = files.filename;
console.log('Files uploading...');
}
else{
alert(files.filename);
}
},
/*xhrFields: {
onprogress: function(e) {
if(e.lengthComputable) {
let percentCompleted = e.loaded / evt.total;
pBar.style.width = percentComplete;
pBar.innerHTML = percentComplete + "%";
console.log('Percent complete : ' + percentComplete);
}
}
}*/
xhr: function(){
let xhr = $.ajaxSettings.xhr();
xhr.upload.onprogress = function(e) {
let percentCompleted = e.loaded / evt.total * 100;
pBar.style.width = percentComplete;
pBar.innerHTML = percentComplete + "%";
console.log('Percent complete : ' + percentComplete);
};
return xhr;
}
});
//});
};
This is just the upload code block. The sending of the data from client-side to server-side works perfectly but it makes be suspicious as the 'console.log' calls aren't being triggered when the code runs through it. Is it so that the data is, somehow, being submitted normally and this code is doing nothing.
EDIT2 :
A new JS function:
function upload() {
console.log('Upload function begins');
let pBar = document.getElementsByClassName('progress-bar')[0],
progressWindow = document.getElementById('modal-progress'),
formData = new FormData(document.forms.form),
xhr = new XMLHttpRequest(),
percent = 0;
console.log('Form Data created');
// Start upload
xhr.upload.onloadstart = function() {
//$('#modal-progress').hide().fadeIn();
//progressWindow
};
// Track upload progress
xhr.upload.onprogress = function(event) {
percent = parseInt(event.loaded / event.total * 100);
pBar.innerHTML = percent + "%";
pBar.style.width = percent + "%";
//console.log(percent + '% completed');
//console.log('Uploaded event.loaded of event.total');
};
// Report if ends with an error
xhr.upload.onerror = function() {
console.log('An error has occurred')
};
// Track completion: Both successful or not
xhr.upload.onloadend = function() {
//$('#modal-progress').fadeOut().hide();
console.log('Upload complete with or without error ' + xhr.status);
};
// Track progress: Triggered on successful completion
xhr.upload.onload = function() {
console.log('Uploading complete');
progressWindow.hidden = True;
};
xhr.open("POST", "{% url 'add_data_sets' %}", true);
// The 'setRequestHeader' function can only be called when xhr is opened.
//xhr.setRequestHeader('csrfmiddlewaretoken', '{{ csrf_token }}');
//xhr.setRequestHeader('test-info', 'something');
xhr.setRequestHeader('Content-Type', 'application/gzip');
xhr.send(formData);
}
The function works fine now. It sends the data completely fine but on the development server console screen I get this error.
Forbidden (CSRF token missing or incorrect.): /accounts/add_data_set/
[22/Feb/2020 15:36:06] "POST /accounts/add_data_set/ HTTP/1.1" 403 2513
I even checked logged the POST data being send to the server and it does contain the csrf token
<QueryDict: {'csrfmiddlewaretoken': ['WREoIV0aY4B2XyrU7d9Qw8kMwiokXqwWsmbc2QSHX5VQ0EaYjjeuv7PeysMJjecp'], 'user_name': ['rakesh'], 'dataset': ['r'], 'creation_date': ['2020-02-22'], 'beamline': ['r']}>
I'm kind of confused. Is this an issue?
If you have any fileuploader plugin their documentations will have everything, or if you want normal file upload input you can post them by binding file input to form data and then post on action which will manipulate form data and save images, then you can return save image and display, this way you can achieve simple ajax upload.
var form_data = new FormData();
var totalFiles = document.getElementById('file').files.length;
var count = 0;
for (var i = 0; i < totalFiles; i++) {
var file = document.getElementById('file').files[i];
count++;
form_data.append("file", file);
}
$.ajax({
url: "/uploadingaction",
dataType: 'json',
contentType: false,
processData: false,
data: form_data,
type: 'POST',
success: function (files, response, xhr, pd) {
$('yourloaderid').hide();
if (files.status != false) {
$('#displayid').val('/location/' + files.filename);
var filedata = files.filename;
} else {
alert(files.filename);
}
}
})

File Upload Progress Bar with Multiple and Different Inputs(MVC)

I searched the internet and found this JavaScript and jQuery template for a file upload progress bar that works 100% fine(given the fact that you only use one form input).
My situation is that I need to pass one file and 4 other inputs like text and select to a Controller Action. The action works fine. My problem is to pass all these values through ajax to the Action whilst maintaining the progress bar functionality.
Action Parameters
[HttpPost]
public ActionResult Add_Attachment_to_Process(int id, int Department_id, HttpPostedFileBase Attachment, string sel_checkTask, string cbx_checkTask = null)
HTML
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<form method="post" enctype="multipart/form-data" action="/Processes/Add_Attachment_to_Process" id="myform">
<input type="file" id="media" name="file" />
<div class="input-group mb-3">
<div class="input-group-prepend">
<div class="input-group-text">
<input type="checkbox" aria-label="Checkbox for following text input" id="cbx_checkTask" name="cbx_checkTask">
<span id="span_checkTask">Link Task</span>
</div>
</div>
<select class="form-control" id="sel_checkTask" name="sel_checkTask" style="width : 700px;" disabled>
#foreach (var t in Model.User_Tasks)
{
<option value="#t.Task_Discription">#t.Task_Discription - #t.Key_Terms</option>
}
</select>
</div>
<input id="id" name="id" value="#ViewBag.process_id " />
<input id="Department_id" name="Department_id" value="#ViewBag.Department_id" />
<input type="submit" />
</form>
<div class="progress" style="width:40%">
<div id="uploadprogressbar" class="progress-bar" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" style="width:0%">
0%
</div>
</div>
JavaScript
$(document).ready(function () {
$("#myform").on('submit', function (event) {
event.preventDefault();
var formData = new FormData($("#myform")[0]);
$.ajax({
xhr: function () {
var xhr = new window.XMLHttpRequest();
xhr.upload.addEventListener('progress', function (e) {
if (e.lengthComputable) {
console.log('Bytes Loaded: ' + e.loaded);
console.log('Total Size: ' + e.total);
console.log('Percentage Uploaded: ' + ((e.loaded / e.total) * 100) + '%');
var percent = Math.round((e.loaded / e.total) * 100);
$("#uploadprogressbar").html(percent + '%');
$("#uploadprogressbar").width(percent + '%');
}
});
return xhr;
},
type: 'POST',
url: '/Processes/Add_Attachment_to_Process',
data: formData,
processData: false,
contentType: false,
success: function () {
alert('File Uploaded');
},
error: function (xhr, status, error) {
var errorMessage = xhr.status + ': ' + xhr.statusText;
alert('Error - ' + errorMessage);
}
});
});
});
AS per the discussion above, try this sort of pattern to better see what values are not being sent
let f = new FormData();
f.append('id', getYouFormValue("id"));
f.append('sel_checkTask', getYouFormValue("sel_checkTask"));
f.append('cbx_checkTask ', getYouFormValue("cbx_checkTask "));
if (form.File) {
f.append('File', getYouFormValue("file"));
}
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: f
}
return fetch(`/Processes/Add_Attachment_to_Process`, requestOptions)
.then(handleResponse)
.then(result => {
//do stuff
});
function handleResponse(response) {
return response.text().then(text => {
const data = text && JSON.parse(text);
if (!response.ok) {
if (response.status === 401) {
console.log('not logged in')
}
const error = (data && data.message) || data.title || response.statusText;
return Promise.reject(error);
}
return data;
});
}
function getYouFormValue(element){
let val = document.getElementById(element);
if(!val){
console.log('empty value');
return null;
}
return val;
}

Why are the functions being called in my angular html bindings receiving null values?

I am creating a simple web app that represents something like a trip planner. Just offers some simple itinerary options from a list of locations in a DB I made. My feed page is supposed to display pictures of each attraction, and each attraction is a clickable card that will lead you to another page with some more info on that attraction.
//
// init app
var tripperApp = angular.module('tripperApp', ["ui.router", 'ionic', 'LocalStorageModule']);
//
// setup routing
tripperApp.config(function($stateProvider, $urlRouterProvider) {
// For any unmatched url, redirect to /state1
$urlRouterProvider.otherwise("/splash");
// Now set up the states
$stateProvider
.state('splash', {
url: "/splash",
templateUrl: "partials/splash.html",
controller: "splashCtrl"
})
.state("login", {
url: "/login",
templateUrl: "partials/login.html",
controller: "loginCtrl"
})
.state("signup", {
url: "/signup",
templateUrl: "partials/signup.html",
controller: "signupCtrl"
})
.state("reset-password", {
url: "/reset-password",
templateUrl: "partials/reset-password.html",
controller: "resetPasswordCtrl"
})
.state("set-password", {
url: "/set-password/:user_id/:password_reset_hash",
templateUrl: "partials/set-password.html",
controller: "resetPasswordCtrl"
})
.state("feed", {
url: "/feed",
templateUrl: "partials/feed.html",
controller: "feedCtrl"
})
.state("wishlist", {
url: "/wishlist",
templateUrl: "partials/feed.html",
controller: "wishlistCtrl"
})
.state("specificwishlist", {
url: "/wishlist/:user_id",
templateUrl: "partials/feed.html",
controller: "wishlistCtrl"
})
.state("share", {
url: "/share",
templateUrl: "partials/share.html",
controller: "shareCtrl"
})
.state("attraction", {
url: "/attraction/:attraction_id",
templateUrl: "partials/attraction.html",
controller: "attractionCtrl"
});
});
This is my app.js file where I initialize all my routings and controllers. I think I am literally having problems with everything (I have a backend and everything in Nodejs, but that isn't working right because all of the values it receives is null - more on that later...).
The main problem I am seeing is that nearly all the data I am receiving is null. Nothing is really loading right - but I know that I have a proper connection to my DB. Because there are some places where I can see the correct results. Let me explain:
This is my services/feed.js file (I don't think it's very important):
//
// feed service: used to get setup filters and then query for a feed using those filters
tripperApp.factory("feedService", ["$rootScope", "api", function($rootScope, api) {
var feedService = {};
api.get("filters").success(function(data, status, headers, config) {
$rootScope.filters = data;
})
.error(function(data, status, headers, config) {
// #TODO - gracefully handle error
});
feedService.runSearch = function($rootScope, session, callback) {
// add on more results
api.post("search", {
"start": $rootScope.currentSpot
})
.success(function(data, status, headers, config) {
// copy results into array
for (var i = 0; i < data.length; i++) {
$rootScope.results.push(data[i]);
}
$rootScope.currentSpot += data.length;
// call callback
callback(true); // success!
})
.error(function(data, status, headers, config) {
callback(false); // error!
});
};
feedService.getWishlist = function($rootScope, session, callback) {
// get all results
api.post("wishlist", {
"attraction_ids": session.picks
})
.success(function(data, status, headers, config) {
// copy results into array
$rootScope.results = data;
callback(true); // success!
})
.error(function(data, status, headers, config) {
callback(false); // error!
});
};
// column-related functions
$rootScope.columns = function() {
var columns = 1;
var windowWidth = window.innerWidth;
if (windowWidth > 1300) {
columns = 5;
} else if (windowWidth > 1100) {
columns = 4;
} else if (windowWidth > 640) {
columns = 3;
} else if (windowWidth > 300) {
columns = 2;
}
var rv = [];
for (var i = 0; i < columns; i++) {
rv.push(i);
}
return rv;
}
$rootScope.getColumn = function(data, columnNumber) {
var columns = $rootScope.columns().length;
var rv = [];
for (var i = columnNumber; i < data.length; i += columns) {
rv.push(data[i]);
}
return rv;
}
$rootScope.columnClasses = function() {
var columns = $rootScope.columns().length;
return {
"col-33": (columns == 3),
"col-25": (columns == 4),
"col-20": (columns == 5),
"col-50": (columns == 2)
};
}
// generate thumbnail of a url
$rootScope.thumb = function(filename) {
return filename + ".thumb.jpeg";
}
return feedService;
}]);
This is my controller (controller/feed.js) file:
//
// feed controller
tripperApp.controller("feedCtrl", function($scope, $rootScope, session, feedService, $ionicScrollDelegate) {
// setup feed page (only once)
if (typeof($rootScope.feed) == "undefined") {
// first time loading - setup scope
$rootScope.feed = {
results: [],
currentSpot: 0
}
}
// auto scroll down (if appropriate)
window.setTimeout(function() {
if (typeof($rootScope.feed.lastScrollPosition) != "undefined") {
$ionicScrollDelegate.$getByHandle('feedScroll').scrollTo(0, $rootScope.feed.lastScrollPosition, false);
}
}, 0);
// tell feed template that this is NOT for a wish
$scope.wishlist = false;
// don't display share button on feed
$scope.displayShare = function() {
return false;
}
// query and display results
$scope.loadResults = function() {
feedService.runSearch($rootScope.feed, session, function(success) {
if (!success) {
// #TODO - handle error
}
$scope.$broadcast('scroll.infiniteScrollComplete');
});
}
// whether an attraction is picked
$scope.attractionIsPicked = function(attraction_id) {
return session.picks.indexOf(attraction_id) != -1;
};
// when a result is picked/unpicked
$scope.resultPicked = function(result) {
var attraction_id = result.id;
console.log(result);
console.log(attraction_id);
if (session.picks.indexOf(attraction_id) == -1) {
// not yet picked, so lets pick it
session.pickPlace(attraction_id);
} else {
// its picked, so lets unpick it
session.unpickPlace(attraction_id);
}
}
$rootScope.clickedResult = undefined;
$scope.resultClicked = function(result) {
$rootScope.clickedResult = result;
console.log("result: \n");
console.log(result);
$rootScope.feed.lastScrollPosition =
$ionicScrollDelegate.$getByHandle('feedScroll').getScrollPosition().top;
}
});
There are a couple of functions I am specifically having problems with:
resultPicked() in controller/feed.js and thumb() in services/feed.js. I am getting null values for both of these.
My partial/feed.html file is as such:
<ion-view>
<ion-header-bar class="bar bar-positive" align-title="center">
<div class="buttons">
<button class="button ion-navicon-round button-light" ng-click="toggleLeftMenu(); " style="font-size: 22px"></button>
</div>
<h1 class="title" ng-show="wishlist==false">New York Attractions</h1>
<h1 class="title" ng-show="wishlist==true">
<span ng-show="feed.user.name == 'Your'">Your</span>
<span ng-show="feed.user.name != 'Your'">{{feed.user.name}}'s</span>
Wish List
</h1>
<div class="buttons" ng-show="displayShare()">
<button class="button ion-android-share button-light" style="font-size: 22px" ng-click="sharePage();"></button>
</div>
</ion-header-bar>
<ion-content class="padding feed-page" delegate-handle="feedScroll">
<ion-list>
<!-- display results in columns -->
<div class="row">
<div class="col" ng-class="columnClasses()" ng-repeat="column in columns()" >
<div class="card" ng-repeat="result in getColumn(feed.results, column)" ng-show="feed.results.length > 0">
<div class="item item-image">
<a>
<img ng-src="{{thumb(result.src)}}" ui-sref="attraction({attraction_id: result.id})" ng-click="resultClicked(result)">
</a>
</div>
<div class="item item-bottom-sect">
<div class="row">
<div class="col-75 col-wrap">
<a ui-sref="attraction({attraction_id: result.id})" ng-click="resultClicked(result)" class="item-title">{{result.title}}</a>
</div>
<div class="col-25 right col-wrap">
<span ng-click="resultPicked(result)">
<span class="ion-heart heart heart-selected" ng-show="attractionIsPicked(result.id)" class="heart-selected"></span>
<span class="ion-heart heart" ng-show="!attractionIsPicked(result.id)"></span>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</ion-list>
<ion-infinite-scroll
ng-if="!resultsLoaded"
on-infinite="loadResults()"
distance="10%">
</ion-infinite-scroll>
</ion-content>
</ion-view>
Now, in my feed, there are two called to resultClicked() and one to resultPicked(). resultClicked(result) will return results just fine - but resultPicked(results) will always gets a null value! Also, thumb(result.src) always gets a null value. I have absolutely ZERO idea what - this is just things I'm confirming from console.logs. Is there anything glaringly wrong with the code?
Edit:
Here is a picture displaying what I am talking about. The cards are all "broken links" because the value im getting back is null (undefined) as shown in console. The "results" are also null, as shown in the console, when called from resultPicked(), but not from resultClicked().

Prevent ng-repeat until request data is fully loaded

Im trying to prevent ng-repeat runs before the request data is fully loaded into an array, I've done it using a timeout but it only works when the request takes less than I say
Here is my HTML:
<div class="blocks-container" ng-init="loadProjects()" ng-controller="buildMonitorController">
<div class="row">
<!-- <div> -->
<div class="col-xs-12 col-sm-6 col-md-3 col-lg-2 block animate"
ng-if="!errorDialogActive && finishedRequest"
ng-repeat="build in builds.builds.build track by build._id | orderBy:'lastBuildDetails.startDate' : true"
ng-class="{'running': project.running ,'block-green': build._status ==='SUCCESS','block-red': build._status==='FAILURE'}"
id="{{build._id}}">
<div class="title-container"><p>{{build._buildTypeId}}</p></div>
<div class="update-container col-xs-12">
<time>{{ build.lastBuildDetails.startDate | date : 'dd.MM.yyyy H:mm:s'}}</time>
</div>
</div>
<!--</div>-->
</div>
<!-- Start error state dialog -->
<div ng-include src="'views/main/error-dialog.html'"></div>
And Here is my AngularJS:
$scope.refreshBuilds = function () {
$scope.errorList.length = 0
//#TODO remove this part right after the API is working
//Init
var suffix = '';
var randomnumber = Math.floor(Math.random() * 3);
//simulate mock by random number
switch (randomnumber) {
case 1:
suffix = '-success';
break;
case 2:
suffix = '-less';
break;
default:
break;
}
var url = 'mock/builds'+suffix+'.xml';
console.log('url: ' + url)
$http({
method: 'GET',
url: url,
headers: {
Authorization: 'Basic AAA'
}
}).success(function (data, status) {
//Recive builds from xml and reset scope
var buildsToFilter = new X2JS().xml_str2json(data);
$scope.errorDialogActive = false;
//filter builds which have a no build API detail status
if (buildsToFilter.builds.build !== undefined) {
angular.forEach(buildsToFilter.builds.build, function (build, index) {
$http({
method: 'GET',
url: 'mock/build-'+build._id+'.xml',
headers: {
Authorization: 'Basic AAA'
}
}).success(function (buildDetailData) {
$scope.errorDialogActive = false;
//push detail data into build array
buildsToFilter.builds.build[index].lastBuildDetails = new X2JS().xml_str2json(buildDetailData).build;
console.log(buildsToFilter.builds.build[index]);
}).error(function (data, status) {
$scope.errorDialogActive = true;
//remove build from index if no build detail was found
buildsToFilter.builds.build.splice(index, 1);
$scope.setError(status, '', '');
}).then(function () {
//after filtering builds to display, setup builds $scope for FrontEnd
$scope.builds = buildsToFilter;
});
});
} else {
}
}).error(function (data, status) {
//active dialog if error happens & set error
$scope.errorDialogActive = true;
$scope.setError(status, 'builds', '');
}).then(function () {
$timeout(function () {
$scope.finishedRequest = true;
}, 5000);
//refresh right after proceed and a little delay
$timeout(function () {
console.log('Starting refresh');
$scope.refreshBuilds();
}, 21000);
})
};
You can put condition
ng-show=builds.builds.build.length > 0
So when the builds.builds.build have data then only it will be displayed
You already have that
ng-if="!errorDialogActive && finishedRequest"
in place, but you never set $scope.finishedRequest = false. What if you simply modify your refreshBuilds function like this:
$scope.refreshBuilds = function () {
$scope.finishedRequest = false;
$scope.errorList.length = 0;
// etc
Does it work then in the way you wanted?

Non-functioning view when sharing controller

I have a simple app that fetches images from Flickr and renders them. The application is divided into two views, SearchView and PhotoListView. Previously, when treating these as one view, everything worked fine, and photos were rendered. Now, when sharing controller, both are rendered but the list of photos is never populated. When debugging I can see that the photos are indeed fetched.
I'm terribly new at Angular, so I really don't have any good guesses at what the problem could be, but possibly that the two views don't share scope properly?
Here's the routing (using ui-router):
// app.js
'use strict';
angular.module('pruApp', ['ui.state'])
.config(function ($httpProvider, $stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/photos');
$stateProvider
.state('/', {
url: '/photos',
views: {
'SearchView': {
templateUrl: '/views/search.html',
controller: 'PhotoCtrl'
},
'PhotoListView': {
templateUrl: '/views/photo-list.html',
controller: 'PhotoCtrl'
}
}
});
// Remove X-Requested-With header to enable CORS
delete $httpProvider.defaults.headers.common['X-Requested-With'];
});
The controller and factory that talks to the Flickr service:
// photo.js
'use strict';
angular.module('pruApp')
.controller('PhotoCtrl', function ($scope, PhotoFactory) {
$scope.search = function() {
PhotoFactory.getPhotos($scope.searchQuery).then(function (data) {
$scope.photos = [];
var parsedData = angular.fromJson(data);
var items = parsedData.photos.photo;
for (var i = 0; i < items.length; ++i) {
var photo = items[i];
$scope.photos.push({
title: photo.title,
image: 'http://farm' + photo.farm + '.staticflickr.com/' + photo.server + '/' + photo.id + '_' + photo.secret + '_m.jpg',
link: 'http://www.flickr.com/photos/' + photo.owner + '/' + photo.id
});
}
});
};
});
angular.module('pruApp')
.factory('PhotoFactory', function ($http, $q) {
return {
_get: function(url) {
var deferred = $q.defer();
$http.get(url)
.success(function (data) {
deferred.resolve(data);
})
.error(function (data, status) {
deferred.reject('An error occured: ' + status);
});
return deferred.promise;
},
getPhotos: function (searchQuery) {
return this._get('http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=<MY_KEY>&tags=' + searchQuery + '&format=json&nojsoncallback=1');
}
};
});
This is where the views are injected:
<!-- index.html -->
<div class="main" role="main">
<div class="search" ui-view="SearchView"></div>
<div class="results" ui-view="PhotoListView"></div>
</div>
Search template:
<!-- search.html -->
<h2 class="struct">Search</h2>
<form>
<fieldset>
<div class="cell">
<input type="search" name="search-query" class="search-query" placeholder="Search photos by tags (e.g. lolcat, rageface, bronie)" ng-model="searchQuery" required>
</div>
<div class="cell">
<button type="submit" name="search-button" class="search-button" ng-click="search()">Search</button>
</div>
</fieldset>
</form>
Photo list template:
<!-- photo-list.html -->
<h2 class="struct">Search results</h2>
<ul>
<li ng-repeat="photo in photos">
<h3>{{ photo.title }}</h3>
<img ng-src="{{ photo.image }}" alt="{{ photo.title }}">
<p class="author">Author: <span>{{ photo.author }}</span></p>
</li>
</ul>
So your problem seems to be that you are calling the method to get the photos on one instance of the controller but that doesn't share data with your other controller. On first read I had missed your factory definition, still the problem is the variable in scope on the controller is in a different instance.
The way you can handle this is by always returning a promise from the factory/service to the controller, the in the controller using the promise to assign the scope. This way if the service has data already available it will automatically populate in your controllers scope, and if not as soon as it comes available it can be populated.
Really it looks like you're doing what I'm saying in the paragraph above but I don't see where the search function is called within the controller. Essentially you should just have something in the controller that is populating the $scope.photos array directly off the service. Then separately you should have your search function which fires off the call to the service passing along the parameter.
Another option is to $watch your properties in the factory/service and update a variable in the $scope of the controller on changes.
angular.module('pruApp')
.factory('PhotoFactory', function ($http, $q) {
return {
photos: [],
_get: function(url) {
var deferred = $q.defer();
$http.get(url)
.success(function (data) {
this.photos = [];
var parsedData = angular.fromJson(data);
var items = parsedData.photos.photo;
for (var i = 0; i < items.length; ++i) {
var photo = items[i];
this.photos.push({
title: photo.title,
image: 'http://farm' + photo.farm + '.staticflickr.com/' + photo.server + '/' + photo.id + '_' + photo.secret + '_m.jpg',
link: 'http://www.flickr.com/photos/' + photo.owner + '/' + photo.id
});
}
deferred.resolve(data);
})
.error(function (data, status) {
deferred.reject('An error occured: ' + status);
});
return deferred.promise;
},
getPhotos: function (searchQuery) {
return this._get('http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=<MY_KEY>&tags=' + searchQuery + '&format=json&nojsoncallback=1');
}
};
});
and in the controller
angular.module('pruApp')
.controller('PhotoCtrl', function ($scope, PhotoFactory) {
$scope.$watch(function () { return PhotoFactory.photos; }, function(data) {
$scope.photos = data;
}); // initialize the watch
$scope.search = function() {
PhotoFactory.getPhotos($scope.searchQuery);
};
}
});

Categories

Resources