Hi I have a problem with $bind, I am binding a model and outputting the models via a ng-repeat. The ng-repeat outputs the stored data and also offers some fields for adding/changing data. The changes are reflected in the scope but are not being synced to Firebase.
Is this a problem with my implementation of $bind?
The HTML:
<iframe id="fpframe" style="border: 0; width: 100%; height: 410px;" ng-if="isLoaded"></iframe>
<form>
<ul>
<li ng-repeat="asset in upload_folder" ng-class="{selected: asset.selected}">
<div class="asset-select"><input type="checkbox" name="selected" ng-model="asset.selected"></div>
<div class="asset-thumb"></div>
<div class="asset-details">
<h2>{{asset.filename}}</h2>
<p><span class="asset-filesize" ng-if="asset.size">Filesize: <strong><span ng-bind-html="asset.size | formatFilesize"></span></strong></span> <span class="asset-filetype" ng-if="asset.filetype">Filetype: <strong>{{asset.filetype}}</strong></span> <span class="asset-dimensions" ng-if="asset.width && asset.height">Dimensions: <strong>{{asset.width}}x{{asset.height}}px</strong></span> <span class="asset-type" ng-if="asset.type">Asset Type: <strong>{{asset.type}}</strong></span></p>
<label>Asset Description</label>
<textarea ng-model="asset.desc" cols="10" rows="4"></textarea>
<label>Creator</label>
<input type="text" ng-model="asset.creator" maxlength="4000">
<label>Release Date</label>
<input type="text" ng-model="asset.release">
<label for="CAT_Category">Tags</label>
<input type="text" ng-model="asset.tags" maxlength="255">
</div>
</li>
</ul>
</form>
The Controller: (fpKey is a constant that stores the Filepicker API key)
.controller('AddCtrl',
['$rootScope', '$scope', '$firebase', 'FBURL', 'fpKey', 'uploadFiles',
function($rootScope, $scope, $firebase, FBURL, fpKey, uploadFiles) {
// load filepicker.js if it isn't loaded yet, non blocking.
(function(a){if(window.filepicker){return}var b=a.createElement("script");b.type="text/javascript";b.async=!0;b.src=("https:"===a.location.protocol?"https:":"http:")+"//api.filepicker.io/v1/filepicker.js";var c=a.getElementsByTagName("script")[0];c.parentNode.insertBefore(b,c);var d={};d._queue=[];var e="pick,pickMultiple,pickAndStore,read,write,writeUrl,export,convert,store,storeUrl,remove,stat,setKey,constructWidget,makeDropPane".split(",");var f=function(a,b){return function(){b.push([a,arguments])}};for(var g=0;g<e.length;g++){d[e[g]]=f(e[g],d._queue)}window.filepicker=d})(document);
$scope.isLoaded = false;
// Bind upload folder data to user account on firebase
var refUploadFolder = new Firebase(FBURL.FBREF).child("/users/" + $rootScope.auth.user.uid + "/upload_folder");
$scope.upload_folder = $firebase(refUploadFolder);
$scope.upload_folder.$bind($scope,'upload_folder');
// default file picker options
$scope.defaults = {
mimetype: 'image/*',
multiple: true,
container: 'fpframe'
};
// make sure filepicker script is loaded before doing anything
// i.e. $scope.isLoaded can be used to display controls when true
(function chkFP() {
if ( window.filepicker ) {
filepicker.setKey(fpKey);
$scope.isLoaded = true;
$scope.err = null;
// additional picker only options
var pickerOptions = {
services:['COMPUTER', 'FACEBOOK', 'GMAIL']
};
var storeOptions = {
location: 'S3',
container: 'imagegrid'
};
var options = $.extend( true, $scope.defaults, pickerOptions );
// launch picker dialog
filepicker.pickAndStore(options, storeOptions,
function(InkBlobs){
uploadFiles.process(InkBlobs, $scope.upload_folder);
},
function(FPError){
$scope.err = FPError.toString();
}
);
} else {
setTimeout( chkFP, 500 );
}
})();
}])
I also have a service handling the input from Filepicker, this creates new entries in the firebase at the reference that is bound (using Firebase methods rather than AngularFire maybe this breaks the binding?)
.service('uploadFiles', ['$rootScope', 'FBURL', function($rootScope, FBURL) {
return {
process: function(InkBlobs, upload_folder) {
var self = this;
var countUpload = 0;
// write each blob to firebase
angular.forEach(InkBlobs, function(value, i){
var asset = {blob: value};
// add InkBlob to firebase one it is uploaded
upload_folder.$add(asset).then( function(ref){
self.getDetails(ref);
countUpload++;
});
});
// wait for all uploads to complete before initiating next step
(function waitForUploads() {
if ( countUpload === InkBlobs.length ) {
self.createThumbs(upload_folder, { multi: true, update: false, location: 'uploads' });
} else {
setTimeout( waitForUploads, 500 );
}
})();
},
getDetails: function(ref) {
// after InkBlob is safely stored we will get additional asset data from it
ref.once('value', function(snap){
filepicker.stat(snap.val().blob, {size: true, mimetype: true, filename: true, width: true, height: true},
function(asset) {
// get asset type and filetype from mimetype
var mimetype = asset.mimetype.split('/');
asset.type = mimetype[0].replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
asset.filetype = mimetype[1];
// add metadata to asset in upload folder
ref.update(asset);
});
});
},
createThumbs: function(ref, options) {
var self = this;
// default options
options.multi = options.multi || false;
options.update = options.update || false;
options.location = options.location || 'asset';
// if pathbase is not provided generate it based on provided location
if (!options.pathbase) {
if (options.location === 'assets') {
options.pathbase = FBURL.LIBRARY + "/assets/";
} else if (options.location === 'uploads') {
options.pathbase = "/users/" + $rootScope.auth.user.uid + "/upload_folder/";
} else {
throw new Error('SERVICE uploadFiles.createThumbs: options.location is not valid must be assets or uploads');
}
}
var generateThumb = function(blob, path) {
filepicker.convert( blob,
{ width: 200, height: 150, fit: 'crop' },
{ location: 'S3', access: 'public', container: 'imagegrid', path: '/thumbs/' },
function(tnInkBlob){
var refThumbBlob = new Firebase(FBURL.FBREF).child(path);
refThumbBlob.set(tnInkBlob);
},
function(FPError){
alert(FPError);
},
function(percentage){
// can use to create progress bar
}
);
};
if (options.multi) {
// look at all assets in provided ref, if thumbnail is mission or update options is true generate new thumb
angular.forEach(ref, function(value, key){
if (typeof value !== 'function' && (!value.tnblob || options.update)) {
// thumb doesn't exist, generate it
var blob = value.blob;
var path = options.pathbase + key + '/tnblob';
generateThumb(blob, path);
}
});
} else {
// getting thumbnail for a single asset
var refAsset = new Firebase(FBURL.FBREF).child(options.pathbase + ref);
var blob = refAsset.val().blob;
var path = options.pathbase + ref + '/tnblob';
generateThumb(blob, path);
}
}
};
}]);
So to recap, data is being saved to /users/$rootScope.auth.user.uid/upload_folder and this is being rendered in the HTML. Changes in the HTML form are reflected in the scope but not in Firebase, despite the binding:
var refUploadFolder = new Firebase(FBURL.FBREF).child("/users/" + $rootScope.auth.user.uid + "/upload_folder");
$scope.upload_folder = $firebase(refUploadFolder);
$scope.upload_folder.$bind($scope,'upload_folder');
Any ideas as to why this is? Is my implementation incorrect or am I somehow breaking the binding? Is $bind even supposed to work with ng-repeat in this manner?
Thanks
Shooting myself for how simple this is, the error was in how I defined the binding. You can't set the binding on itself, you need two separate objects in the scope...
The firebase reference $scope.syncdata loads the initial data and all modifications made to $scope.upload_folder will be synced to firebase.
var refUploadFolder = new Firebase(FBURL.FBREF).child("/users/" + $rootScope.auth.user.uid + "/upload_folder");
$scope.syncdata = $firebase(refUploadFolder);
$scope.syncdata.$bind($scope,'upload_folder');
Related
I have a component with prop List. List is list of input files. At once input changed I add another one input.
Weird behavior if I try to delete .
https://jsfiddle.net/apokjqxx/115/
removeAnother: function(item) {
var vm = this;
var num = vm.$parent.cornerList.indexOf(item);
vm.$parent.cornerList.splice(num, 1);
},
How to reproduce:
choose file in first input
choose file in second input (will added after step 1)
choose file in third input (will added after step 2)
then click to remove on first item in list
Expected: removed first item but has removed last added
Use a key on your list.
<div v-for="(item, index) in list" :key="item.id">
I modified your fiddle to generate an id for each object added to the cornerList array.
var formuploadimage = Vue.extend({
template: '#template-form-upload-image',
props: {
list: {
type: Array
}
},
data: function() {
return {
isFileChanged: false
}
},
watch: {
validCnt: function() {
},
},
methods: {
onFileChange: function(item) {
var vm = this;
let id = Math.max.apply(Math, vm.$parent.cornerList.map(c => c.id)) + 1
var newItem = {id};
vm.$parent.cornerList.push(newItem);
},
removeAnother: function(item) {
var vm = this;
var num = vm.$parent.cornerList.indexOf(item);
vm.$parent.cornerList.splice(num, 1);
},
},
});
var app = new Vue({
el: ".lists-wrappers",
data: {
cornerList: [{id: 1}],
},
components: {
formuploadimage: formuploadimage
},
methods: {
},
});
.select-file{
width:250px;
border:1px solid red;
}
<script src="https://unpkg.com/vue#2.4.4/dist/vue.js"></script>
<div class="lists-wrappers">
<formuploadimage :list="cornerList"></formuploadimage>
</div>
<script type="text/x-template" id="template-form-upload-image">
<div>
<div v-for="(item, index) in list" :key="item.id">
<div class="select-file">
REMOVE<br/>
<label for="file-input">
+Add photo
</label>
<input type="file" #change="onFileChange(item)" />
</div>
</div>
</div>
</script>
I'm quite new to AngularJS and struggling a bit to have some input fields updated after an autocompletion event using google maps.
The idea is that when the user inputs his city/zip code, I would update 3 fields which are themselves linked to an object.
So far, I managed to have a working code except that sometimes the fields are not updated immediately : I have to autocomplete twice so that the good value will appear in the fields.
I've tweaked an existing angular directive in order to get what I want but since this is new to me, I dont know if I'm using the correct approach.
Below is the JS directive I use :
angular.module( "ngVilleAutocomplete", [])
.directive('ngAutocomplete', function($parse) {
return {
scope: {
details: '=',
ngAutocomplete: '=',
options: '=',
data: '='
},
link: function(scope, element, attrs, model) {
//options for autocomplete
var opts
//convert options provided to opts
var initOpts = function() {
opts = {}
if (scope.options) {
if (scope.options.types) {
opts.types = []
opts.types.push(scope.options.types)
}
if (scope.options.bounds) {
opts.bounds = scope.options.bounds
}
if (scope.options.country) {
opts.componentRestrictions = {
country: scope.options.country
}
}
}
}
initOpts()
//create new autocomplete
//reinitializes on every change of the options provided
var newAutocomplete = function() {
scope.gPlace = new google.maps.places.Autocomplete(element[0], opts);
google.maps.event.addListener(scope.gPlace, 'place_changed', function() {
scope.$apply(function() {
scope.details = scope.gPlace.getPlace();
//console.log(scope.details)
var HasCP = false;
for (var i=0 ; i<scope.details.address_components.length ; i++){
for (var j=0 ; j<scope.details.address_components[i].types.length ; j++){
if (scope.details.address_components[i].types[j] == 'postal_code' && scope.data.CP != 'undefined'){
scope.data.CP = scope.details.address_components[i].long_name;
HasCP = true;
} else if (scope.details.address_components[i].types[j] == 'locality' && scope.data.Ville != 'undefined') {
scope.data.Ville = scope.details.address_components[i].long_name;
} else if (scope.details.address_components[i].types[j] == 'country' && scope.data.Pays != 'undefined') {
scope.data.Pays = scope.details.address_components[i].long_name;
}
}
}
if (!HasCP){
var latlng = {lat: scope.details.geometry.location.lat(), lng: scope.details.geometry.location.lng()};
var geocoder = new google.maps.Geocoder;
geocoder.geocode({'location': latlng}, function(results, status) {
if (status === google.maps.GeocoderStatus.OK) {
for (var i=0 ; i<results[0].address_components.length ; i++){
for (var j=0 ; j<results[0].address_components[i].types.length ; j++){
if (results[0].address_components[i].types[j] == 'postal_code' && scope.data.CP != 'undefined'){
scope.data.CP = results[0].address_components[i].long_name;
console.log('pc trouvé :' + scope.data.CP);
}
}
}
}
});
}
//console.log(scope.data)
scope.ngAutocomplete = element.val();
});
})
}
newAutocomplete()
//watch options provided to directive
scope.watchOptions = function () {
return scope.options
};
scope.$watch(scope.watchOptions, function () {
initOpts()
newAutocomplete()
element[0].value = '';
scope.ngAutocomplete = element.val();
}, true);
}
};
});
The matching HTML code is below :
<div class="form-group">
<lable>Code postal : </label>
<input type="text" id="Autocomplete" class="form-control" ng-autocomplete="cities_autocomplete" details="cities_autocomplete_details" options="cities_autocomplete_options" data="client" placeholder="Code postal" ng-model="client.CP" />
</div>
<div class="form-group">
<lable>Ville : </label>
<input type="text" id="Autocomplete" class="form-control" ng-autocomplete="cities_autocomplete" details="cities_autocomplete_details" options="cities_autocomplete_options" data="client" placeholder="Ville" ng-model="client.Ville" />
</div>
<div class="form-group">
<lable>Pays : </label>
<input type="text" class="form-control" name="Pays" ng-model="client.Pays" placeholder="Pays" />
</div>
You'll see that I pass the "client" object directly to my directive which then updates this object. I expected angular to update the html page as soon as the values of the client object are updated but I will not always be the case :
If I search twice the same city, the values are not updated
If I search a city, Google wont send me a zip code so I have to do another request to the geocoding service and I get the zipcode in return but while my client.CP field is correctly updated, changes are not visible in the CP input field until I do another search.
Thanks in advance for any advice on what I'm doing wrong.
I am currently adding a CSV email-list option to an existing AngularJS-based site. I use Papa Parse to parse the CSV straight to objects. What I need to do is have these results add up to the HTML and filter them so that only the email addresses will appear in the results, which will then later be counted for sending. How can I filter a CSV parse reult so that it only displays email addresses and ignores all the other values? Naturally, this would have to be done using just Angular to avoid conflicts.
Here's the Papa Parse section:
$('input[type=file]').parse({
config: {header: true,
step: function(results, handle) {
rows = _.union(rows, results.data);
console.log("Row data:", results.data);
console.log("Row errors:", results.errors);
// Define HTML output
//
for (var i = 0; i < $files.length; i++) {
var file = $files[i];
}
$.each(results.data, function(i, el) {
var row = $('<ng-form ng-repeat="row in rows"/>');
row.append($('').text(i));
$.each(el, function(j, cell) {
if (cell !== "")
row.append($('<td type="text" class="form-control csv-form medium" ng-model="row.email"></td>').text(cell));
});
$("#results").append(row);
});
}
}
});
function FileUploadCtrl(scope) {
scope.setFiles = function(element) {
scope.$apply(function(scope) {
console.log('files:', element.files);
// Turn the FileList object into an Array
scope.files = []
for (var i = 0; i < element.files.length; i++) {
scope.files.push(element.files[i])
}
scope.progressVisible = false
});
};
scope.uploadFile = function() {
var fd = new FormData()
for (var i in scope.files) {
fd.append("uploadedFile", scope.files[i])
}
var xhr = new XMLHttpRequest()
xhr.upload.addEventListener("progress", uploadProgress, false)
xhr.addEventListener("load", uploadComplete, false)
xhr.addEventListener("error", uploadFailed, false)
xhr.addEventListener("abort", uploadCanceled, false)
xhr.open("POST", "/fileupload")
scope.progressVisible = true
xhr.send(fd)
}
}
}
);
},
This should be the HTML part:
<div ng-file-select="onFileSelect($files, angular.element(this).scope())" data-multiple="true" ng-list ng-model="csv" id="fileToUpload" title="Select file" ng-change="angular.element(this).scope().setFiles(this)">
<i class="fa fa-upload"></i> {{ 'Import CSV' | Translator }}
I'm using AngularJS to populate my datatable. What I want to know is how can I populate the datatable based on the dropdownlist
This is my dropdownlist
<div>
Get Users with Role:
<select id="ddlRole" data-ng-model="selectedRole" data-ng-change="populateDataTable()" data-ng-options="v.name for (k,v) in roles"></select>
<input type="hidden" value="{{selectedRole}}" />
</div>
This is my angular code
$scope.roles = [
{name: 'XXX' },
{name: 'YYY' }
];
$scope.selectedRole = $scope.roles[0];
//onchange event
$scope.populateDataTable = function () {
$scope.selectedRole = $scope.selectedRole.name;
RefreshDataTable(); //TODO
};
How can I change this to make an ajax call to retreive the data, populate the datatable based on the dropdownlist value and retain the value of dropdownlist as well.
I'm sure we can do this using JQuery but I dont want to mix these and make a mess. Is there any way I can acheive this using AngularJS?
Here is a simple data table directive:
appModule.directive('dataTable', [function () {
return function (scope, element, attrs) {
// apply DataTable options, use defaults if none specified by user
var options = {};
if (attrs.myTable.length > 0) {
options = scope.$eval(attrs.myTable);
} else {
options = {
"bStateSave": true,
"iCookieDuration": 2419200, /* 1 month */
"bJQueryUI": true,
"bPaginate": false,
"bLengthChange": false,
"bFilter": false,
"bInfo": false,
"bDestroy": true
};
}
// Tell the dataTables plugin what columns to use
// We can either derive them from the dom, or use setup from the controller
var explicitColumns = [];
element.find('th').each(function (index, elem) {
explicitColumns.push($(elem).text());
});
if (explicitColumns.length > 0) {
options["aoColumns"] = explicitColumns;
} else if (attrs.aoColumns) {
options["aoColumns"] = scope.$eval(attrs.aoColumns);
}
// aoColumnDefs is dataTables way of providing fine control over column config
if (attrs.aoColumnDefs) {
options["aoColumnDefs"] = scope.$eval(attrs.aoColumnDefs);
}
if (attrs.fnRowCallback) {
options["fnRowCallback"] = scope.$eval(attrs.fnRowCallback);
}
// apply the plugin
var dataTable = element.dataTable(options);
// watch for any changes to our data, rebuild the DataTable
scope.$watch(attrs.aaData, function (value) {
var val = value || null;
if (val) {
dataTable.fnClearTable();
dataTable.fnAddData(scope.$eval(attrs.aaData));
}
});
if (attrs.useParentScope) {
scope.$parent.dataTable = dataTable;
} else {
scope.dataTable = dataTable;
}
};
}]);
Then initialize it in your controller. Override fnServerData method, append your selected value (selected role) and filter data on server side.
$scope.overrideOptions = {
"bStateSave": true,
"iDisplayLength": 8,
"bProcessing": false,
"bServerSide": true,
"sAjaxSource": 'Data/Get',
"bFilter": false,
"bInfo": true,
"bLengthChange": false,
"sServerMethod": 'POST', ,
"fnServerData": function(sUrl, aoData, fnCallback, oSettings) {
var data = {
dataTableRequest: aoData,
selectedDropDownValue: $scope.selectedRole
};
$http.post(sUrl, data).success(function (json) {
if (json.sError) {
oSettings.oApi._fnLog(oSettings, 0, json.sError);
}
$(oSettings.oInstance).trigger('xhr', [oSettings, json]);
fnCallback(json);
});
}
};
var columnDefs = [
{
"mData": "id",
"bSortable": false,
"bSearchable": false,
"aTargets": ['tb-id']
},
{
"mData": "data",
"aTargets": ['tb-data']
}
];
Refresh the datatable on select change.
$scope.populateDataTable = function () {
if ($scope.dataTable) {
$scope.dataTable.fnDraw();
}
};
Html markup:
<table class="display m-t10px" data-table="overrideOptions" ao-column-defs="columnDefs">
<thead>
<tr>
<th class="tb-id"></th>
<th class="tb-data></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
Hope above your code is in controller.
Inject $http and make a $http get or post call
$scope.populateDataTable = function () {
$scope.selectedRole = $scope.selectedRole.name;
$http.get('api/controller', function(result){
//response from the service call in result
});
};
I'm trying to figure out how to update my models with services, when I get a response from the server.
My JSON structure:
var dgs = [{id :1,
driver:'Sam',
type: 'bus',
segments:[ {id:1,origin:'the bakery',arrival:'the store'},
{id:2,origin:'the store' ,arrival:'somewhere'}]
},
{ ... },
{ ... }
];
index.html:
<body ng-controller="dgCtrl">
<div ng-repeat="dg in dgs" drivegroup></div>
</body>
dg.html:
<div> {{ dg.driver }} </div>
<div> {{ dg.type }} </div>
<div ng-repeat="seg in dg.segments" segments></div>
segments.html:
<div>
<input ng-model="seg.origin" ng-blur="updateSegment()"/></div>
<div>
<input ng-model="seg.arrival" ng-blur="updateSegment()"/></div>
My controller is the following:
function dgCtrl($scope,$http,DriveGroup,Segment) {
var segment = new Segment;
$scope.dgs = DriveGroup.query(); // load & display all drive groups
$scope.updateSegment = function() {
var seg = this.seg;
segment.seg = this.seg;
segment.id = this.seg.id;
segment.$update(function(data,x) {
// seg.origin = data.origin - This updates the origin.
seg = data; // This doesn't work. How can I do this?
});
}
My services:
angular.module('dgService',['ngResource']).
factory("DriveGroup",function($resource) {
return $resource(
'/path/dgs',
{},
{update:{method:'PUT'}}
);
});
angular.module('Segment',['ngResource']).
factory('Segment',function($resource) {
return $resource(
'/path/seg/:id',
{id:'#id'},
{update:{method:'PUT'}}
);
});
My problem is I'm not sure how to update on a per-segment basis using $resource. I want to make a change on a certain segment, so I make a put request. I get the response from the server and want to update my model (which isn't working).
I'm new to Angular, so if you see any other blatant mistakes/or structure issues suggestions are appreciated. Thanks.
EDIT:
The following achieves the results I want, but is this correct?
$scope.updateSegment = function() {
var seg = this.seg,
segment = new Segment(seg);
segment.$update(function(data,x) {
angular.extend(seg,data);
});
}