Compile mustache partials using external json data - json

I'm using mustache partials for our internal documentation and for testing - as described in this other SO question how to have grunt task render mustache partials to static HTML, and I'd now like to make the partials data driven using external json files. The json files would be named the same as the partials and will contain mock data that would compile with the partials.
Any suggestions on how to get this working?

I've updated the hogan.js file to read in data.json files that have the same name as the partials:
module.exports = function(grunt) {
// Grunt utilities.
var task = grunt.task,
file = grunt.file,
utils = grunt.util,
log = grunt.log,
verbose = grunt.verbose,
fail = grunt.fail,
option = grunt.option,
config = grunt.config,
template = grunt.template,
_ = utils._
// external dependencies
var fs = require('fs'),
hogan = require('hogan');
// ==========================================================================
// TASKS
// ==========================================================================
grunt.registerMultiTask('hogan', 'Compile mustache files to HTML with hogan.js', function() {
var data = this.data,
src = grunt.file.expandFiles(this.file.src),
dest = grunt.template.process(data.dest),
// Options are set in gruntfile
defaults = {
production: false,
docs: false,
title: 'Sellside',
setAccount: 'NA',
setSiteId: 'NA',
layout: 'docs/templates/layout.mustache',
paths: {},
partials: {},
partialsData: {},
},
options = _.extend(defaults, this.data.options || {})
!src && grunt.warn('Missing src property.')
if(!src) return false
!dest && grunt.warn('Missing dest property')
if(!dest) return false
var done = this.async()
var srcFiles = file.expandFiles(src)
if(options.paths.partials) {
var partials = grunt.file.expandFiles(options.paths.partials);
log.writeln('Compiling Partials...');
partials.forEach(function(filepath) {
var filename = _.first(filepath.match(/[^\\\/:*?"<>|\r\n]+$/i)).replace(/\.mustache$/, '');
log.writeln(filename.magenta);
var dataFilepath = filepath.replace(/\.mustache$/, '.json');
var partial = fs.readFileSync(filepath, 'utf8');
options.partials[filename] = hogan.compile(partial);
// if a data file exists, read in the data
if(fs.existsSync(dataFilepath)) {
options.partialsData[filename] = grunt.file.readJSON(dataFilepath);
}
});
log.writeln();
}
try {
options.layout = fs.readFileSync(options.layout, 'utf8')
options.layout = hogan.compile(options.layout, {
sectionTags: [{
o: '_i',
c: 'i'
}]
})
} catch(err) {
grunt.warn(err) && done(false)
return
}
srcFiles.forEach(function(filepath) {
var filename = _.first(filepath.match(/[^\\\/:*?"<>|\r\n]+$/i)).replace(/\.mustache$/, '')
grunt.helper('hogan', filepath, filename, options, function(err, result) {
err && grunt.warn(err) && done(false)
if(err) return
file.write(dest.replace('FILE', filename), result)
})
})
done()
})
// ==========================================================================
// HELPERS
// ==========================================================================
grunt.registerHelper('hogan', function(src, filename, options, callback) {
log.writeln('Compiling ' + filename.magenta);
var page = fs.readFileSync(src, 'utf8'),
html = null,
layout = options.layout,
context = {};
context[filename] = 'active';
context._i = true;
context.production = options.production;
context.docs = options.docs;
context.setAccount = options.setAccount;
context.setSiteId = options.setSiteId;
var title = _.template("<%= page == 'Index' ? site : page + ' · ' + site %>")
context.title = title({
page: _(filename).humanize().replace('css', 'CSS'),
site: options.title
})
try {
page = hogan.compile(page, {
sectionTags: [{
o: '_i',
c: 'i'
}]
})
context = _.extend(context, options.partialsData);
options.partials.body = page;
page = layout.render(context, options.partials)
callback(null, page)
} catch(err) {
callback(err)
return
}
})
};
A simple example of an alert.json file:
{
"modifier": "alert-warning",
"alertType": "Warning!",
"message": "Best check yo self, you're not looking too good."
}
alert.mustache
alerts
<div class="row-fluid">
<div class="alert {{alert.modifier}}">
<button type="button" class="close" data-dismiss="alert">×</button>
<strong>{{alert.alertType}}</strong>
{{alert.message}}
</div>
</div>
</div><!-- /container -->
I like this next example better because you can create different data to use
in the same partials:
alert.json
{
"default": {
"modifier": "alert-warning",
"alertType": "Warning!",
"message": "Best check yo self, you're not looking too good.!"
},
"error": {
"modifier": "alert-error",
"alertType": "Error!!!",
"message": "You did something horribily wrong!!!"
},
"success": {
"modifier": "alert-success",
"alertyType": "Success!!!",
"message": "The record has been successfully saved..."
}
}
alert.mustache
<!-- partial -->
<div class="container">
<h1>alerts</h1>
<div class="row-fluid">
<div class="alert {{modifier}}">
<button type="button" class="close" data-dismiss="alert">×</button>
<strong>{{alertType}}</strong>
{{message}}
</div>
</div>
</div><!-- /container -->
partials.mustache --- this is where the alert partial is referenced
{{#alert.default}}
{{> alert }}
{{/alert.default}}
{{#alert.error}}
{{> alert }}
{{/alert.error}}
{{#alert.success}}
{{> alert }}
{{/alert.success}}
Let me know if this helps.
Thanks,
Brian

Related

How to load json from file and set it as global variable in Vue?

I'm new to Vue. I want to read employeeId from a login form and ust it to load some json files named according as employeeId.json like (10000001.json, 20000001.json) and set the json object as a global variable so I can easily access it in all components.
Firstly, I don't know how to dynamically load json files. Using import sees not work. Some one suggested using require should work. But there are not many examples, I don't know where to put require...
Secondly, how do I set the json as global after the employeeId props in? I'm very confused where to put it (inside the export default or not? inside methods or not? or inside created/mounted or not?) and where to use this or not...
This is the script section of my headerNav.vue file.
<script>
//**I placed them here now, it works, but employeeId is hard coded...
import json10000001 from "./json/10000001.json";
import json20000001 from "./json/20000001.json";
import json30000001 from "./json/30000001.json";
// var employeeId = employeeIdFromLogin;
var jsonForGlobal;
var employeeId = 10000001;
var jsonFileCurrentObj;
if (employeeId == "10000001") {
jsonForGlobal = jsonFileCurrentObj = json10000001;
} else if (employeeId == "20000001") {
jsonForGlobal = jsonFileCurrentObj = json20000001;
} else if (employeeId == "30000001") {
jsonForGlobal = jsonFileCurrentObj = json30000001;
}
export default {
// props:{
// employeeIdFromLogin: String,
// },
props:['employeeIdFromLogin'],
jsonForGlobal,
// employeeIdFromLogin,
data() {
return {
docked: false,
open: false,
position: "left",
userinfo: {},
jsonFileCurrent: jsonFileCurrentObj,
// employeeIdFromLogin: this.GLOBAL3.employeeIdFromLogin
// jsonFile: currentJsonFile
};
},
mounted() {
//**I tried put it here, not working well...
// var employeeId = this.employeeIdFromLogin;
// // var jsonForGlobal;
// console.log("headernav.employeeIdFromLogin="+this.employeeIdFromLogin);
// // var employeeId = 10000001;
// var jsonFileCurrentObj;
// if (employeeId == "10000001") {
// this.jsonForGlobal = this.jsonFileCurrentObj = json10000001;
// } else if (employeeId == "20000001") {
// this.jsonForGlobal = this.jsonFileCurrentObj = json20000001;
// } else if (employeeId == "30000001") {
// this.jsonForGlobal = this.jsonFileCurrentObj = json30000001;
// }
},
methods: {
switchPage(pageName) {
this.$emit("switchPage", pageName);
}
//**I don't know how to use the require...
// var employeeId = 10000001;
// getJsonFile(employeeId) {
// this.currentJsonFile = require("../assets/json/" + employeeId + ".json");
// }
}
};
You might want to use vuex to manage global store. But if you don't want includes Vuex, there is a simpler way to have global state:
Define globalStore.js
// globalStore.js
export const globalStore = new Vue({
data: {
jsonForGlobal: null
}
})
then import it and use in component:
import {globalStore} from './globalStore.js'
export default {
props: ['employeeIdFromLogin'],
data: function ()
return {
jsonLocal: globalStore.jsonForGlobal,
jsonFileCurrent: null
}
},
watch: {
employeeIdFromLogin: {
handler(newVal, oldVal) {
const data = require('./json/' + this.employeeIdFromLogin + '.json')
this.jsonFileCurrent = data
globalStore.jsonForGlobal = data
}
}
}
}

Passing drop down list option to controller

I have a drop down list in the form of the select tag as shown below:
<select id = "1">
<option>Amy</option>
<option>Gi-Anne</option>
</select>
I want to pass the selected option - either Amy or Gi Anne to this method of the controller.
public String name (string nameSelected)
{
var query = new NameQuery();
if(nameSelected.Equals('Amy'))
{run a specific query}
else if(nameSelected.Equals('Gi-Anne'))
{run a specific query}
}
How do I pass the parameter of the selected drop down list value to the controller? Appreciate your help and thanks in advance.
This is 'fairly' easy using AngularJS, see this Plunk for a (simulated) example.
The HTML changes to this:
<body ng-app="myApp">
<div ng-controller="myController">
State: {{onChangeText}}
<br/>
<select ng-model="selectedItemId" id="itemList" ng-change="onChange()">
<option value="{{item.id}}" ng-selected="{{item.id == selectedItemId}}" ng-repeat="item in items">{{item.name}}</option>
</select>
<br/>
{{selectedQuery}}
</div>
</body>
With a controller like this:
app.controller("myController", [
"$scope",
"$http",
function($scope, $http){
var self = {};
self.simulatedGetQuery = function() {
console.log($scope.selectedItemId);
$scope.selectedQuery = "";
switch($scope.selectedItemId) {
case "1":
$scope.selectedQuery = "Query Amy";
break;
case "2":
$scope.selectedQuery = "Query Gi-Anne";
break;
}
};
self.httpGetQuery = function() {
$http({
method: 'GET',
url: 'http://somehostname/action/' + $scope.selectedItemId
}).then(function successCallback(response) {
$scope.selectedQuery = response;
}, function errorCallback(response) {
});
};
// -- SCOPED -- //
$scope.selectedItemId = 0;
$scope.items = [
{
"id": 1,
"name": "Amy"
},
{
"id": 2,
"name": "Gi-Anne"
}
];
$scope.onChange = function() {
$scope.onChangeText = "simulated GET triggered.";
self.simulatedGetQuery();
// Use this for actual GET
// self.httpGetQuery
};
// --- //
$scope.onChangeText = "waiting for user input";
$scope.selectedQuery = "no query selected. Chose a person for a valid query.";
}]);
It would need to be fleshed out in a real environment, but I think it will do for a simulated test. Check the scripts in the Plunk for a more detailed perspective on how to do this. All of this is clientside.
The URL of the $http call would be to your backend (MVC or Web API) controller.

How do i test my custom angular schema form field

I've just started developing with Angular schema form and I'm struggling to write any tests for my custom field directive.
I've tried compiling the schema form html tag which runs through my directives config testing it's display conditions against the data in the schema. However it never seems to run my controller and I can't get a reference to the directives HTML elements. Can someone give me some guidance on how to get a reference to the directive? Below is what I have so far:
angular.module('schemaForm').config(['schemaFormProvider',
'schemaFormDecoratorsProvider', 'sfPathProvider',
function(schemaFormProvider, schemaFormDecoratorsProvider, sfPathProvider) {
var date = function (name, schema, options) {
if (schema.type === 'string' && schema.format == 'date') {
var f = schemaFormProvider.stdFormObj(name, schema, options);
f.key = options.path;
f.type = 'date';
options.lookup[sfPathProvider.stringify(options.path)] = f;
return f;
}
};
schemaFormProvider.defaults.string.unshift(date);
schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'date',
'app/modules/json_schema_form/schema_form_date_picker/schema_form_date_picker.html');
}]);
var dateControllerFunction = function($scope) {
$scope.isCalendarOpen = false;
$scope.showCalendar = function () {
$scope.isCalendarOpen = true;
};
$scope.calendarSave = function (date) {
var leaf_model = $scope.ngModel[$scope.ngModel.length - 1];
var formattedDate = $scope.filter('date')(date, 'yyyy-MM-dd');
leaf_model.$setViewValue(formattedDate);
$scope.isCalendarOpen = false;
};
};
angular.module('schemaForm').directive('schemaFormDatePickerDirective', ['$filter', function($filter) {
return {
require: ['ngModel'],
restrict: 'A',
scope: false,
controller : ['$scope', dateControllerFunction],
link: function(scope, iElement, iAttrs, ngModelCtrl) {
scope.ngModel = ngModelCtrl;
scope.filter = $filter
}
};
}]);
<div ng-class="{'has-error': hasError()}">
<div ng-model="$$value$$" schema-form-date-picker-directive>
<md-input-container>
<!-- showTitle function is implemented by ASF -->
<label ng-show="showTitle()">{{form.title}}</label>
<input name="dateTimePicker" ng-model="$$value$$" ng-focus="showCalendar()" ng-disabled="isCalendarOpen">
</md-input-container>
<time-date-picker ng-model="catalogue.effectiveFrom" ng-if="isCalendarOpen" on-save="calendarSave($value)" display-mode="date"></time-date-picker>
</div>
<!-- hasError() defined by ASF -->
<span class="help-block" sf-message="form.description"></span>
</div>
And the spec:
'use strict'
describe('SchemaFormDatePicker', function() {
var $compile = undefined;
var $rootScope = undefined;
var $scope = undefined
var scope = undefined
var $httpBackend = undefined;
var elem = undefined;
var html = '<form sf-schema="schema" sf-form="form" sf-model="schemaModel"></form>';
var $templateCache = undefined;
var directive = undefined;
beforeEach(function(){
module('app');
});
beforeEach(inject(function(_$compile_, _$rootScope_, _$templateCache_, _$httpBackend_) {
$compile = _$compile_
$rootScope = _$rootScope_
$httpBackend = _$httpBackend_
$templateCache = _$templateCache_
}));
beforeEach(function(){
//Absorb call for locale
$httpBackend.expectGET('assets/locale/en_gb.json').respond(200, {});
$templateCache.put('app/modules/json_schema_form/schema_form_date_picker/schema_form_date_picker.html', '');
$scope = $rootScope.$new()
$scope.schema = {
type: 'object',
properties: {
party: {
title: 'party',
type: 'string',
format: 'date'
}}};
$scope.form = [{key: 'party'}];
$scope.schemaModel = {};
});
describe("showCalendar", function () {
beforeEach(function(){
elem = $compile(html)($scope);
$scope.$digest();
$httpBackend.flush();
scope = elem.isolateScope();
});
it('should set isCalendarOpen to true', function(){
var result = elem.find('time-date-picker');
console.log("RESULT: "+result);
));
});
});
});
If you look at the below example taken from the project itself you can see that when it uses $compile it uses angular.element() first when setting tmpl.
Also, the supplied test module name is 'app' while the code sample has the module name 'schemaForm'. The examples in the 1.0.0 version of Angular Schema Form repo all use sinon and chai, I'm not sure what changes you would need to make if you do not use those.
Note: runSync(scope, tmpl); is a new addition for 1.0.0 given it is now run through async functions to process $ref includes.
/* eslint-disable quotes, no-var */
/* disabling quotes makes it easier to copy tests into the example app */
chai.should();
var runSync = function(scope, tmpl) {
var directiveScope = tmpl.isolateScope();
sinon.stub(directiveScope, 'resolveReferences', function(schema, form) {
directiveScope.render(schema, form);
});
scope.$apply();
};
describe('sf-array.directive.js', function() {
var exampleSchema;
var tmpl;
beforeEach(module('schemaForm'));
beforeEach(
module(function($sceProvider) {
$sceProvider.enabled(false);
exampleSchema = {
"type": "object",
"properties": {
"names": {
"type": "array",
"description": "foobar",
"items": {
"type": "object",
"properties": {
"name": {
"title": "Name",
"type": "string",
"default": 6,
},
},
},
},
},
};
})
);
it('should not throw needless errors on validate [ノಠ益ಠ]ノ彡┻━┻', function(done) {
tmpl = angular.element(
'<form name="testform" sf-schema="schema" sf-form="form" sf-model="model" json="{{model | json}}"></form>'
);
inject(function($compile, $rootScope) {
var scope = $rootScope.$new();
scope.model = {};
scope.schema = exampleSchema;
scope.form = [ "*" ];
$compile(tmpl)(scope);
runSync(scope, tmpl);
tmpl.find('div.help-block').text().should.equal('foobar');
var add = tmpl.find('button').eq(1);
add.click();
$rootScope.$apply();
setTimeout(function() {
var errors = tmpl.find('.help-block');
errors.text().should.equal('foobar');
done();
}, 0);
});
});
});

Adding a picture to the MEAN.JS sample with Angular-file-upload

I am using MEAN.JS (https://github.com/meanjs/mean) and angular-file-upload (https://github.com/danialfarid/angular-file-upload).
The "Article" sample provided by MEAN.JS contains two fields named "title" and "content". I want to modify that and add a "picture" field allowing a user to upload a picture.
I understand that I have to modify 3 files in MEAN.JS:
~myproject/app/models/article.server.model.js
~myproject/public/modules/articles/controllers/articles.client.controller.js
~myproject/public/modules/articles/views/create-article.client.view.html
However, I can not modify them successfully.
My solution uses angular-file-upload on the client and uses connect-multiparty to handle the file upload.
The images are stored directly in the database which limits their size. I have not included the required code to check the image size.
set up
bower install ng-file-upload --save
bower install ng-file-upload-shim --save
npm i connect-multiparty
npm update
all.js
add angular-file-upload scripts
...
'public/lib/ng-file-upload/FileAPI.min.js',
'public/lib/ng-file-upload/angular-file-upload-shim.min.js',
'public/lib/angular/angular.js',
'public/lib/ng-file-upload/angular-file-upload.min.js',
...
config.js
Inject angular-file-upload dependency
...
var applicationModuleVendorDependencies = ['ngResource', 'ngAnimate', 'ui.router', 'ui.bootstrap', 'ui.utils', 'angularFileUpload'];
...
article.client.controller.js
Use angular-file-upload dependency
angular.module('articles').controller('ArticlesController', ['$scope', '$timeout', '$upload', '$stateParams', '$location', 'Authentication', 'Articles',
function($scope, $timeout, $upload, $stateParams, $location, Authentication, Articles) {
$scope.fileReaderSupported = window.FileReader !== null;
// Create new Article
$scope.create = function(picFile) {
console.log('create');
console.log(picFile);
var article = new Articles({
title: this.title,
content: this.content,
image: null
});
console.log(article);
$upload.upload({
url: '/articleupload',
method: 'POST',
headers: {'Content-Type': 'multipart/form-data'},
fields: {article: article},
file: picFile,
}).success(function (response, status) {
$location.path('articles/' + response._id);
$scope.title = '';
$scope.content = '';
}).error(function (err) {
$scope.error = err.data.message;
});
};
$scope.doTimeout = function(file) {
console.log('do timeout');
$timeout( function() {
var fileReader = new FileReader();
fileReader.readAsDataURL(file);
console.log('read');
fileReader.onload = function(e) {
$timeout(function() {
file.dataUrl = e.target.result;
console.log('set url');
});
};
});
};
$scope.generateThumb = function(file) {
console.log('generate Thumb');
if (file) {
console.log('not null');
console.log(file);
if ($scope.fileReaderSupported && file.type.indexOf('image') > -1) {
$scope.doTimeout(file);
}
}
};
}
create-article.client.view.html
update the create view to handle file selection and upload
<section data-ng-controller="ArticlesController">
<div class="page-header">
<h1>New Article</h1>
</div>
<div class="col-md-12">
<form name="articleForm" class="form-horizontal" data-ng-submit="create(picFile)" novalidate>
<fieldset>
<div class="form-group" ng-class="{ 'has-error': articleForm.title.$dirty && articleForm.title.$invalid }">
<label class="control-label" for="title">Title</label>
<div class="controls">
<input name="title" type="text" data-ng-model="title" id="title" class="form-control" placeholder="Title" required>
</div>
</div>
<div class="form-group">
<label class="control-label" for="content">Content</label>
<div class="controls">
<textarea name="content" data-ng-model="content" id="content" class="form-control" cols="30" rows="10" placeholder="Content"></textarea>
</div>
</div>
<div class="form-group">
<label class="control-label" for="articleimage">Article Picture</label>
<div class="controls">
<input id="articleimage" type="file" ng-file-select="" ng-model="picFile" name="file" accept="image/*" ng-file-change="generateThumb(picFile[0], $files)" required="">
<br/>
<img ng-show="picFile[0].dataUrl != null" ng-src="{{picFile[0].dataUrl}}" class="img-thumbnail" height="50" width="100">
<span class="progress" ng-show="picFile[0].progress >= 0">
<div style="width:{{picFile[0].progress}}%" ng-bind="picFile[0].progress + '%'" class="ng-binding"></div>
</span>
<span ng-show="picFile[0].result">Upload Successful</span>
</div>
</div>
<div class="form-group">
<input type="submit" class="btn btn-default" ng-disabled="!articleForm.$valid" ng-click="uploadPic(picFile)">
</div>
<div data-ng-show="error" class="text-danger">
<strong data-ng-bind="error"></strong>
</div>
</fieldset>
</form>
</div>
</section>
view-article.client.view.html
update list view to include image
<img ng-src="data:image/jpeg;base64,{{article.image}}" id="photo-id" width="200" height="200"/>
list-articles.client.view.html
update view to include imgae
<img ng-src="data:image/jpeg;base64,{{article.image}}" id="photo-id" width="40" height="40"/>
article.server.model.js
Add image to database model
...
image: {
type: String,
default: ''
},
...
article.server.routes.js
Add new route for upload use connect-multiparty
...
multiparty = require('connect-multiparty'),
multipartyMiddleware = multiparty(),
...
app.route('/articleupload')
.post(users.requiresLogin, multipartyMiddleware, articles.createWithUpload);
...
article.server.controller.js
Handle new route for upload require fs
...
fs = require('fs'),
...
/**
* Create a article with Upload
*/
exports.createWithUpload = function(req, res) {
var file = req.files.file;
console.log(file.name);
console.log(file.type);
console.log(file.path);
console.log(req.body.article);
var art = JSON.parse(req.body.article);
var article = new Article(art);
article.user = req.user;
fs.readFile(file.path, function (err,original_data) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
}
// save image in db as base64 encoded - this limits the image size
// to there should be size checks here and in client
var base64Image = original_data.toString('base64');
fs.unlink(file.path, function (err) {
if (err)
{
console.log('failed to delete ' + file.path);
}
else{
console.log('successfully deleted ' + file.path);
}
});
article.image = base64Image;
article.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(article);
}
});
});
};
...
Thanks Charlie Tupman for this last solution which works very well.
I simply add the dependencies :
...
multiparty = require('multiparty'),
uuid = require('uuid'),
...
before the exports function and changed two lines to improve the behavior:
...
var destPath = './public/uploads/' + fileName;
article.image = '/uploads/' + fileName;
...
which resolve in:
exports.createWithUpload = function(req, res) {
var form = new multiparty.Form();
form.parse(req, function(err, fields, files) {
var file = req.files.file;
console.log(file.name);
console.log(file.type);
console.log(file.path);
console.log(req.body.article);
var art = JSON.parse(req.body.article);
var article = new Article(art);
article.user = req.user;
var tmpPath = file.path;
var extIndex = tmpPath.lastIndexOf('.');
var extension = (extIndex < 0) ? '' : tmpPath.substr(extIndex);
var fileName = uuid.v4() + extension;
var destPath = './public/uploads/' + fileName;
article.image = '/uploads/' + fileName;
var is = fs.createReadStream(tmpPath);
var os = fs.createWriteStream(destPath);
if(is.pipe(os)) {
fs.unlink(tmpPath, function (err) { //To unlink the file from temp path after copy
if (err) {
console.log(err);
}
});
article.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.jsonp(article);
}
});
} else
return res.json('File not uploaded');
});
};
My working solution in MEAN.js
server model:
image:{
type: String,
default: ''
},
server controller:
var item = new Item(JSON.parse(req.body.item));
item.user = req.user;
if(req.files.file)
item.image=req.files.file.name;
else
item.image='default.jpg';
//item.image=
item.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.jsonp(item);
}
});
server route: (requires multer: "npm install multer --save")
var multer = require('multer');
app.use(multer({ dest: './public/uploads/'}));
frontend angular controller:
$scope.image='';
$scope.uploadImage = function(e){
console.log(e.target.files[0]);
$scope.image=e.target.files[0];
};
// Create new Item
$scope.create = function() {
// Create new Item object
var item = new Items ({
name: this.name,
bought: this.bought,
number: this.number,
description: this.description,
warranty: this.warranty,
notes: this.notes
});
ItemsService.saveItem(item,$scope.image);
};
service which send's the request:
.factory('ItemsService', ['$http','$rootScope', function($http, $rootScope)
{
var service={};
service.saveItem = function(item, image)
{
var fd = new FormData();
fd.append('file', image);
fd.append('item', JSON.stringify(item));
$http.post('items/', fd, {
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
})
.success(function(){
console.log('success add new item');
})
.error(function(e){
console.log('error add new item', e);
});
};
return service;
}
]);
html view:
<div class="form-group">
<label class="control-label" for="name">Image</label>
<div class="controls">
<input type="file" data-ng-model="image" id="image" my-file-upload="uploadImage" required>
{{selectedFile.name}}
</div>
</div>
#john prunell Thank you so much for this, I finally figured it out (took me over a week but i am now much more comfortable with the stack) I have forked it to get it to upload the file to a folder:
'exports.createWithUpload = function(req, res) {
var form = new multiparty.Form();
form.parse(req, function(err, fields, files) {
var file = req.files.file;
console.log(file.name);
console.log(file.type);
console.log(file.path);
console.log(req.body.article);
var art = JSON.parse(req.body.article);
var article = new Article(art);
article.user = req.user;
var tmpPath = file.path;
var extIndex = tmpPath.lastIndexOf('.');
var extension = (extIndex < 0) ? '' : tmpPath.substr(extIndex);
var fileName = uuid.v4() + extension;
var destPath = './uploads/' + fileName;
article.image = fileName;
var is = fs.createReadStream(tmpPath);
var os = fs.createWriteStream(destPath);
if(is.pipe(os)) {
fs.unlink(tmpPath, function (err) { //To unlink the file from temp path after copy
if (err) {
console.log(err);
}
});
article.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.jsonp(article);
}
});
}else
return res.json('File not uploaded');
});
};
What I would like to do now is fork your solution to allow uploading of multiple images in the same way, if you have any insight into how to do this that would be amazing, I will let you know how I get on.

django-rest pagination with angularjs

I have an angularjs app working nicely with django-rest but have hit an issue by introducing pagination. I have a restservice and controller as per the below
// restservices.js
// API call for all images in an event
services.factory('ImageEvent', function ($resource) {
return $resource(rest_api + '/rest/image/?event_id=:eventId&format=json', {}, {
query: { method:'GET', params:{eventId:''}, isArray:true}
})
});
// controllers.js
// all Images in an event
.controller('ImageEventCtrl', ['$scope', '$stateParams', 'ImageEvent', function($scope, $stateParams, ImageEvent) {
$scope.images = ImageEvent.query({eventId: $stateParams.eventId}, function(images) {
});
}])
this returns the following json
[
{
"id": 13,
"title": "01-IMG_4953.JPG",
},
{
"id": 14,
"title": "02-IMG_4975.JPG",
},
{
"id": 15,
"title": "03-IMG_4997.JPG",
}
]
However if I turn on django-rest pagination it returns the following json:
{
"count": 3,
"next": "/rest/image/?event_id=1&page=2",
"previous": null,
"results":
[
{
"id": 13,
"title": "01-IMG_4953.JPG",
},
{
"id": 14,
"title": "02-IMG_4975.JPG",
}
]
}
This change has caused the following console error and everything fails to work:
Error: [$resource:badcfg] Error in resource configuration. Expected response to contain an array but got an object
Changing the restservice to isArray:false has made no difference. Can my controller be re-written to cope with this and in a perfect world also expose the count, next and previous links?
Thanks
Angular-ui has a pagination directive that I've used with Django Rest Framework before.
http://angular-ui.github.io/bootstrap/#/pagination
To load only X amount of items at a time I have done the following below. Note that I'm using the pagination to recreate the django admin feature in angular.
if request.GET.get('page'):
# Get the page number
page = request.GET.get('page')
# How many items per page to display
per_page = data['admin_attrs']['list_per_page']
begin = (int(page) - 1) * per_page
end = begin + per_page
objects = MODEL.objects.all()[begin:end]
# Serializer for your corresponding itmes. This will grab the particular modelserializer
serializer = serializer_classes[MODEL._meta.object_name](
objects, fields=admin_attrs['list_display']
)
data['objects'] = serializer.data
return Response(data)
My angular code to keep track of page and also allow back button functionality and also update the URL:
modelDetails Factory gets generates the url with the correct page number from pagination
app.factory('modelDetails', function($http, $q){
data = {content: null}
var dataFactory = {}
dataFactory.getObjects = function (app, model, page){
var deferred = $q.defer()
$http.get('api/admin/' + app + '/' + model + '/?page=' + page)
.success(function(result) {
deferred.resolve(result);
});
return deferred.promise
};
return dataFactory
});
$scope.loadObjects = function () {
modelDetails.getObjects(app, model, $scope.currentPage)
.then(function (data){
$scope.headers = data.headers;
$scope.admin_attrs = data.admin_attrs;
blank = new Array()
list_display = data.admin_attrs.list_display
$scope.objects = convertObjects(data.objects, list_display)
$scope.numPerPage = data.admin_attrs.list_per_page
$scope.currentPage = $stateParams.p
$scope.maxSize = 20;
$scope.bigTotalItems = data.object_count;
$scope.numPages = Math.ceil(data.object_count / $scope.admin_attrs.list_per_page);
})
.then( function (data) {
$scope.$watch('currentPage + numPerPage', function(oldVal, newVal) {
var begin = (($scope.currentPage - 1) * $scope.numPerPage)
, end = begin + $scope.numPerPage;
if(oldVal != newVal){
$location.search('p', $scope.currentPage)
}
$rootScope.$on('$locationChangeSuccess', function(event) {
$scope.currentPage = $location.search().p
modelDetails.getObjects(app, model, $scope.currentPage)
.then( function (data) {
// convertObjects just reorders my data in a way I want
$scope.objects = convertObjects(data.objects, list_display)
});
});
});
});
}