HTML5 web audio - convert audio buffer into wav file - html

I have an audio buffer rendered using webkitOfflineAudioContext. Now, I wish to export it into a WAV file. How do I do it? I tried using recorder.js but couldn't figure out how to use it. Here's my code: http://jsfiddle.net/GBQV8/.

Here's a gist that should help: https://gist.github.com/kevincennis/9754325.
I haven't actually tested this, so there might be a stupid typo or something, but the basic approach will work (I've done it before).
Essentially, you're going to use the web worker from Recorder.js directly so that you can process one big AudioBuffer all in one shot, rather than recording it incrementally in real-time.
I'll paste the code here too, just in case something happens to the gist...
// assuming a var named `buffer` exists and is an AudioBuffer instance
// start a new worker
// we can't use Recorder directly, since it doesn't support what we're trying to do
var worker = new Worker('recorderWorker.js');
// initialize the new worker
worker.postMessage({
command: 'init',
config: {sampleRate: 44100}
});
// callback for `exportWAV`
worker.onmessage = function( e ) {
var blob = e.data;
// this is would be your WAV blob
};
// send the channel data from our buffer to the worker
worker.postMessage({
command: 'record',
buffer: [
buffer.getChannelData(0),
buffer.getChannelData(1)
]
});
// ask the worker for a WAV
worker.postMessage({
command: 'exportWAV',
type: 'audio/wav'
});

I figured I'd share a working solution that I managed to put together from the Kevin's answer.
Here's the waveWorker.js script:
self.onmessage = function( e ){
var wavPCM = new WavePCM( e['data']['config'] );
wavPCM.record( e['data']['pcmArrays'] );
wavPCM.requestData();
};
var WavePCM = function( config ){
this.sampleRate = config['sampleRate'] || 48000;
this.bitDepth = config['bitDepth'] || 16;
this.recordedBuffers = [];
this.bytesPerSample = this.bitDepth / 8;
};
WavePCM.prototype.record = function( buffers ){
this.numberOfChannels = this.numberOfChannels || buffers.length;
var bufferLength = buffers[0].length;
var reducedData = new Uint8Array( bufferLength * this.numberOfChannels * this.bytesPerSample );
// Interleave
for ( var i = 0; i < bufferLength; i++ ) {
for ( var channel = 0; channel < this.numberOfChannels; channel++ ) {
var outputIndex = ( i * this.numberOfChannels + channel ) * this.bytesPerSample;
var sample = buffers[ channel ][ i ];
// Check for clipping
if ( sample > 1 ) {
sample = 1;
}
else if ( sample < -1 ) {
sample = -1;
}
// bit reduce and convert to uInt
switch ( this.bytesPerSample ) {
case 4:
sample = sample * 2147483648;
reducedData[ outputIndex ] = sample;
reducedData[ outputIndex + 1 ] = sample >> 8;
reducedData[ outputIndex + 2 ] = sample >> 16;
reducedData[ outputIndex + 3 ] = sample >> 24;
break;
case 3:
sample = sample * 8388608;
reducedData[ outputIndex ] = sample;
reducedData[ outputIndex + 1 ] = sample >> 8;
reducedData[ outputIndex + 2 ] = sample >> 16;
break;
case 2:
sample = sample * 32768;
reducedData[ outputIndex ] = sample;
reducedData[ outputIndex + 1 ] = sample >> 8;
break;
case 1:
reducedData[ outputIndex ] = ( sample + 1 ) * 128;
break;
default:
throw "Only 8, 16, 24 and 32 bits per sample are supported";
}
}
}
this.recordedBuffers.push( reducedData );
};
WavePCM.prototype.requestData = function(){
var bufferLength = this.recordedBuffers[0].length;
var dataLength = this.recordedBuffers.length * bufferLength;
var headerLength = 44;
var wav = new Uint8Array( headerLength + dataLength );
var view = new DataView( wav.buffer );
view.setUint32( 0, 1380533830, false ); // RIFF identifier 'RIFF'
view.setUint32( 4, 36 + dataLength, true ); // file length minus RIFF identifier length and file description length
view.setUint32( 8, 1463899717, false ); // RIFF type 'WAVE'
view.setUint32( 12, 1718449184, false ); // format chunk identifier 'fmt '
view.setUint32( 16, 16, true ); // format chunk length
view.setUint16( 20, 1, true ); // sample format (raw)
view.setUint16( 22, this.numberOfChannels, true ); // channel count
view.setUint32( 24, this.sampleRate, true ); // sample rate
view.setUint32( 28, this.sampleRate * this.bytesPerSample * this.numberOfChannels, true ); // byte rate (sample rate * block align)
view.setUint16( 32, this.bytesPerSample * this.numberOfChannels, true ); // block align (channel count * bytes per sample)
view.setUint16( 34, this.bitDepth, true ); // bits per sample
view.setUint32( 36, 1684108385, false); // data chunk identifier 'data'
view.setUint32( 40, dataLength, true ); // data chunk length
for (var i = 0; i < this.recordedBuffers.length; i++ ) {
wav.set( this.recordedBuffers[i], i * bufferLength + headerLength );
}
self.postMessage( wav, [wav.buffer] );
self.close();
};
And here's how you can use it:
async function audioBufferToWaveBlob(audioBuffer) {
return new Promise(function(resolve, reject) {
var worker = new Worker('./waveWorker.js');
worker.onmessage = function( e ) {
var blob = new Blob([e.data.buffer], {type:"audio/wav"});
resolve(blob);
};
let pcmArrays = [];
for(let i = 0; i < audioBuffer.numberOfChannels; i++) {
pcmArrays.push(audioBuffer.getChannelData(i));
}
worker.postMessage({
pcmArrays,
config: {sampleRate: audioBuffer.sampleRate}
});
});
}
It's pretty quickly hacked together so feel free (of course) to fix it up and post a link to a better version in the comments :)

When using recorder.js, make sure you start with recording a piece of audio, and then stop it. After you stopped the recorder you can call the .exportWAV function. The callback contains a blob in wav format. Instead of recording the buffer yourself, you'd better use recorder.js's buffer creation, because if you call exportWAV, it will export the buffer it previously saved. It created the buffer from the source object you entered when creating a new recorder.
var rec = new Recorder(yourSourceObject);
rec.record();
//let it record
rec.stop();
rec.exportWAV(function(blob){
//the generated blob contains the wav file
}])
You can also check out the source code of recorderWorker.js and find out how to convert a buffer to a wav file yourself.

For no real time processing, take a look at OfflineAudioContext.
That might be useful to process audio data as if it was a regular AudioContext, but not in real time. If your data does not com from the microphone, you probably want to want to process it as fast as possible. Then, you'll need OfflineAudioContext to create a buffer before encoding it to wav

Related

Predefined layout not being used by advance slides api on slide create

This is my first almost successful addon. It is a standalone script. I have tested it on several slide decks created by others. In the latest test the Table of Contents which my code creates used a master/layout from the deck rather than the BLANK predefined one I called for in my code?
/**
not using blank predefined blank master
MINI TEST
*/
function miniTest() {
const pres = SlidesApp.getActivePresentation();
const presId = pres.getId();
let updtReqArr = [];
// create request for a Slide to hold the table of contents
let insertIdx = 0;
pageId = miniPage(updtReqArr, insertIdx);
if (updtReqArr.length > 0) {
response = Slides.Presentations.batchUpdate({ 'requests': updtReqArr }, presId);
}
}
/**
* create request for a Slide to hold the table of contents
*/
function miniPage(updtReqArr, insertIdx) {
// console.log('Begin createTocPage - insertIdx: ', insertIdx );
pageId = Utilities.getUuid();
// console.log('pageId: ', pageId);
// base slide object request
slideObj = [{
'createSlide': {
'objectId': pageId,
'insertionIndex': insertIdx,
'slideLayoutReference': {
'predefinedLayout': 'BLANK' // name of master
}
}
}];
// console.log('slideObj: ', JSON.stringify(slideObj));
updtReqArr.push(slideObj);
// console.log('updtReqArr.length: ', updtReqArr.length);
// code that creates page elments, background, etc. removed for simplicity
return pageId;
}
The presentation upon which I encountered this problem was a copy of the GEG Eduprotocols presentation. This is the link to my copy.
https://docs.google.com/presentation/d/1EEUDz0fBXnI4IBT8xlcKJrfPs_KMr5HIY2YfCjDLiuQ/edit?usp=sharing
This is the publicly available source
https://docs.google.com/presentation/d/1i5Hhod8ERu8dfmMk5f-pcuFGHZ6bc96JAe3Pca_rbxY/edit#slide=id.p
The creators used Slidesmania masters and those masters are showing up in the Table of Contents which I added even though I said use BLANK. What am I doing wrong?
Trying to use answer March 31, 2021 - - - - - - - -
Still no luck using an existing BLANK layout in the slidedeck. Really confused by getMasters() and getLayouts(). The following two sets of code find the same 'objectId' which does not work in batch update.
let layoutsArr = pres.getLayouts();
let lArrSize = layoutsArr.length;
for ( i = 0 ; i < lArrSize ; i++ ) {
console.log('layoutsArr[i].getLayoutName(): ', layoutsArr[i].getLayoutName() );
if ( layoutsArr[i].getLayoutName() === 'BLANK' ) {
blankObjId = layoutsArr[i].getObjectId();
blankIdx = i;
console.log('layoutsArr[i] blankIdx: ', blankIdx );
i = presSize + 1;
}
}
let mastersArr = pres.getMasters();
let mArrSize = mastersArr.length;
for ( i = 0 ; i < mArrSize ; i++ ) {
console.log('i: ', i );
layoutsArr = mastersArr[i].getLayouts();
let lArrSize = layoutsArr.length;
for ( j = 0 ; j < lArrSize ; j++ ) {
console.log('j: ', j, ' layoutsArr[j].getLayoutName(): ', layoutsArr[j].getLayoutName() );
if ( layoutsArr[j].getLayoutName() === 'BLANK' ) {
blankObjId = layoutsArr[j].getObjectId();
console.log(' blankObjId: ', blankObjId );
blankIdx = j;
console.log('layoutsArr[i] blankIdx: ', blankIdx );
j = lArrSize + 1;
i = mArrSize + 1;
}
}
}
pageId = Utilities.getUuid();
console.log('pageId: ', pageId);
slideObj = [{
'createSlide': {
'objectId': pageId,
'insertionIndex': insertIdx,
'slideLayoutReference': {
"layoutId": blankObjId
}
}
}];
The batch update code generated:
slideObj: [{"createSlide":{"objectId":"c5f67b57-3318-4e4a-9aff-7d1a09f57464","insertionIndex":0,"slideLayoutReference":{"layoutId":"p12"}}}]
error
// Invalid requests[0].createSlide: The layout (p12) is not present in the current master (gc92c1a471f_0_19804).
I had to add the extra lines as the code was shown as a mashed together paragraph???
Explanation:
Since your slides have a custom template, the predefined layouts have been overwritten by the new template. You need to specify the object ID from the specific layout you want from the slide-mania master.
Sample Code:
/**
not using blank predefined blank master
MINI TEST
*/
function miniTest() {
const pres = SlidesApp.getActivePresentation();
const presId = pres.getId();
const master = pres.getMasters()[1]; // slide-mania
const layout = master.getLayouts()[16].getObjectId(); // blank
let updtReqArr = [];
// create request for a Slide to hold the table of contents
let insertIdx = 0;
pageId = miniPage(updtReqArr, insertIdx, layout); // blank
if (updtReqArr.length > 0) {
response = Slides.Presentations.batchUpdate({ 'requests': updtReqArr }, presId);
}
}
/**
* create request for a Slide to hold the table of contents
*/
function miniPage(updtReqArr, insertIdx, layout) {
// console.log('Begin createTocPage - insertIdx: ', insertIdx );
pageId = Utilities.getUuid();
// console.log('pageId: ', pageId);
// base slide object request
slideObj = [{
'createSlide': {
'objectId': pageId,
'insertionIndex': insertIdx,
'slideLayoutReference': {
'layoutId': layout // object ID of layout
}
}
}];
// console.log('slideObj: ', JSON.stringify(slideObj));
updtReqArr.push(slideObj);
// console.log('updtReqArr.length: ', updtReqArr.length);
// code that creates page elments, background, etc. removed for simplicity
return pageId;
}
To change the created layout, please change the index of master.getLayouts() depending on the order of the layouts in Slide Mania master. You can view them using View menu -> Master.
References:
Predefined Layout

How do you render a line markup with the Markups Extension?

I previously asked question about if there was a programatic way to render markups with the markups extension. This worked, or at least, for text markups! Now I am trying to do the same thing with line markups; however, I am stuck with one issue. How do you add the locations to the markup? I have an array of locations that I am trying to assign to it, but there doesn't seem to be a function, and when I try to directly assign locations with markup.location = [etc], for some reason it changes all the numbers to infinity.
So, how can I assign the location array to the markup?
This is how I am loading them:
let MarkupsCore = Autodesk.Viewing.Extensions.Markups.Core;
let line = new MarkupsCore.MarkupFreehand(25, markupTool); //eslint-disable-line
line.locations = [{x: 2, y: 3}]; //something like this
markupTool.addMarkup(line);
line.setSize({ x: markup.x, y: markup.y}, markup.width, markup.height);
line.updateStyle(true);
The MarkupFreehand cannot be used directly, it should be replaced by EditModeFreehand. It's also not easy to archive your request, to create a line markup, in a few codes. Here is the code snippet I used to create a line markup with the MarkupCore extension:
function createLineStartPt( mousePosX, mousePosY, editor ) {
const editMode = markup.editMode;
editor.snapper && editor.snapper.clearSnapped();
editMode.lastX = editMode.initialX = mousePosX;
editMode.lastY = editMode.initialY = mousePosY;
//set the starting point
const position = editMode.position = editor.clientToMarkups( mousePosX, mousePosY );
editMode.movements = [position];
const size = editMode.size = editor.sizeFromClientToMarkups( 1, 1 );
// Create pen.
editor.beginActionGroup();
const markupId = editor.getId();
const createPen = editMode.createPen( markupId, position, size, 0, [{x: 0, y: 0 }] );
createPen.execute();
editMode.createAbsolutePath( position );
editMode.selectedMarkup = editor.getMarkup( markupId );
editMode.creationBegin();
}
function createLineEndPt( mousePosX, mousePosY, editor ) {
const editMode = markup.editMode;
const selectedMarkup = editMode.selectedMarkup;
if( !selectedMarkup || !editMode.creating )
return;
const movements = editMode.movements;
const location = editor.clientToMarkups( mousePosX, mousePosY );
const dx = editMode.lastX - mousePosX;
const dy = editMode.lastY - mousePosY;
const moveTol = 25; // 5^2, compare to square to avoid using square root of distance
if( movements.length > 1 && (dx*dx + dy*dy) < moveTol ) {
movements[movements.length - 1] = location;
editMode.removeFromAbsolutePath( 1 );
} else {
movements.push( location );
editMode.lastX = mousePosX;
editMode.lastY = mousePosY;
}
editMode.addToAbsolutePath([location]);
const appendPen = editMode.setPen( editMode.position, editMode.size, editMode.absolutePath, true );
appendPen.execute();
}
function endLineDrawing( editor ) {
const editMode = markup.editMode;
if( !editMode.creating )
return editMode.creationEnd();
let movements = editMode.movements;
const cameraWidth = editMode.viewer.impl.camera.right - editMode.viewer.impl.camera.left;
const cameraHeight = editMode.viewer.impl.camera.top - editMode.viewer.impl.camera.bottom;
const cameraDiagSq = cameraWidth * cameraWidth + cameraHeight * cameraHeight;
movements = Autodesk.Viewing.Extensions.Markups.Core.Utils.simplify( movements, cameraDiagSq * 0.00000001, true );
const xs = movements.map(function(item) { return item.x });
const ys = movements.map(function(item) { return item.y });
const l = Math.min.apply(null, xs);
const t = Math.min.apply(null, ys);
const r = Math.max.apply(null, xs);
const b = Math.max.apply(null, ys);
const width = r - l; // Already in markup coords space
const height = b - t; // Already in markup coords space
const position = {
x: l + width * 0.5,
y: t + height * 0.5
};
const size = editMode.size = {x: width, y: height};
// Adjust points to relate from the shape's center
const locations = movements.map(function(point){
return {
x: point.x - position.x,
y: point.y - position.y
};
});
const endPen = editMode.setPen( position, size, locations, false );
endPen.execute();
editMode.creationEnd();
}
Then call them in this way:
// Load the extionsion
let markup;
viewer.loadExtension( 'Autodesk.Viewing.MarkupsCore' )
.then(function( markupsExt ) {
markup = markupsExt;
});
// Enter markup editer mode and change it to freehand mode
markup.enterEditMode();
const freehand = new Autodesk.Viewing.Extensions.Markups.Core.EditModeFreehand( markup );
markup.changeEditMode( freehand );
// Create start point of the line
createLineStartPt( 360.03125, 191.3125, markup );
// Create end point of the line
createLineEndPt( 460.03125, 191.3125, markup );
// Tell the markup tool to finish drawing
endLineDrawing( markup );
Here is the result of the above codes:
Note. All mousePos prefixed variables are coordinates in the client coordinate system in the browser viewport, see below link for details. After getting the mosue's clientX or clientY, you have to minus markup.svg.getBoundingClientRect() to adjust their values.
https://developer.mozilla.org/en-US/docs/Web/CSS/CSSOM_View/Coordinate_systems#Client
Hope it helps!

Parse random String & return Value (JSON)

Edit: It works pretty well now and this makes it possible to reference URLs in a JSON file and return their related pairs (e.g game name / year / image url). Here's the code.
AFRAME.registerComponent('jfetch', {
schema: {},
init: function () {
var url = 'json/text.json';
var request = new XMLHttpRequest();
request.open( 'GET', url, true );
request.addEventListener( 'load', function ( event ) {
var jsongames = JSON.parse( event.target.response )
var keys = Object.keys(jsongames);
var random = jsongames[keys.length * Math.random() << 0];
var games = random.Title + ' (' + random.Developer + ')'
var textEntity = document.querySelector('#text');
textEntity.setAttribute("value", games)
var gurl = random.Imageurl
var sceneEl = document.querySelector('a-scene');
sceneEl.querySelector('a-box').setAttribute('material', {src:gurl});
} );
request.send( null );
}
});
Thanks for the help everyone!
Original Question: I would like to fetch a random "Title" string in my Json file and return its corresponding value. I know the simple test code works but I have no idea how to apply it to an array with lots of objects and to add the random parsing element. Can someone please help me with a solution? This is my remix file.
AFRAME.registerComponent('json-text-loader', {
schema: {},
init: function () {
var textEntity = document.querySelector('#text');
var url = 'json/text.json';
var request = new XMLHttpRequest();
request.open( 'GET', url, true );
request.addEventListener( 'load', function ( event ) {
var jsonGames = JSON.parse( event.target.response )
textEntity.setAttribute("value", jsonGames.Title)
} );
request.send( null );
}
});
You can not manipulate JSON file as you would with an object, because JSON is a string. Thats why we parse them (converting into Javascript object).
From your question I assume that you want to get a random value of key-value pairs, where key is a Title. In that case, first you parse JSON response (as you do already - var jsonGames = JSON.parse( event.target.response )). So now you have your Javascript object to work with - jsonGames and all what is left is to get that random key of it. To do that you can, for ex:
var jsonGames = JSON.parse( event.target.response )
var keys = Object.keys(jsonGames);
var random = jsonGames[keys[keys.length * Math.random() << 0]];
textEntity.setAttribute("value", random.Title)
Please comment if something is not right.

Passing a JSON AngularJS structure to a mongoDB database

I have just recentely used AngularJS to "convert" a data structure I had in pure SVG format into JSON format.
Now, I want to store such a structure in a MongoDB database to start finally connecting some components of the MEAN stack together and start seeing some things working! Basically, I have the following code inside a Webstorm AngularJS project:
JS:
var app = angular.module('app', []);
var RectangleDim=30;
app.controller('MainCtrl', function($scope) {
$scope.graph = {'width': 5000, 'height': 5000};
$scope.circles = [
/* JSON.parse("{\"x\": 85, \"y\": 20, \"r\":15}"),
{"x": 20, "y": 60, "r":20},
{"x": 18, "y": 10, "r":40} */
];
$scope.draw=function(val)
{
// val = document.getElementById("NumQuest").value;
return JSON.parse('{\"cx\":'+val+', "cy": 20, "r":30}');
// $scope.circles.push(JSON.parse('{\"x\":'+val+', "y": 220, "r":30}'));
};
$scope.rectangles = [
// {'x':220, 'y':220, 'width' : 300, 'height' : 100},
// {'x':520, 'y':220, 'width' : 10, 'height' : 10},
];
$scope.DrawRect=function(xpos,ypos) {
return JSON.parse('{\"x\":' + xpos + ', \"y\":' + ypos + ', \"width\":' + RectangleDim + ', \"height\":' + RectangleDim+ '}');
};
$scope.Debug=function(desiredNo){
desiredNo=document.getElementById("NumQuest").value;
for(var i = 0;i < RectangleDim*desiredNo+desiredNo;i++){
$scope.rectangles.push($scope.DrawRect(i+RectangleDim+1,40));
}
};
$scope.DrawLineOdd=function(desiredNo,lineNo,pozY){
var pozX = lineNo*RectangleDim;
var aux = 2*Math.floor(Math.sqrt(desiredNo))-1-2*lineNo;
for (var j = 0; j < aux; j++) {
$scope.rectangles.push($scope.DrawRect(pozX, pozY));//$scope.DrawRect(pozX, pozY);
pozX += RectangleDim;
}
//return aux;
};
$scope.DrawMatrixPerfectProgression=function(desiredNo) {
desiredNo=document.getElementById("NumQuest").value;
var line=0;
var pozy=0;
while(line<Math.floor(Math.sqrt(desiredNo))) {
$scope.DrawLineOdd(desiredNo, line, pozy);
//document.getElementById("val").innerHTML = teste;
line += 1;
pozy+=RectangleDim;
}
//document.getElementById('tablePrint').innerHTML = finalTable;
};
$scope.DrawLineEven=function(desiredNo, lineNo, pozY){
var pozX = lineNo*RectangleDim;
//var pozY = lineno*20;
var aux = 2*Math.floor(Math.sqrt(desiredNo))-2*lineNo;
for (var j = 0; j < aux; j++) {
$scope.rectangles.push($scope.DrawRect(pozX, pozY));
pozX += RectangleDim;
}
//return aux;
};
$scope.DrawMatrixEvenProgression=function(desiredNo) {
desiredNo=document.getElementById("NumQuest").value;
var line=0;
var pozy=0;
while(line<Math.floor((Math.sqrt(4*desiredNo+1)-1)/2)) {
$scope.DrawLineEven(desiredNo, line, pozy);
//document.getElementById("val").innerHTML = teste;
line += 1;
pozy+=RectangleDim;
}
//document.getElementById('tablePrint').innerHTML = finalTable;
};
$scope.AddExtraRectangles=function(desiredNo) {
desiredNo = document.getElementById("NumQuest").value;
var arg1 = desiredNo - ( Math.floor(Math.sqrt(desiredNo))*Math.floor(Math.sqrt(desiredNo)));
var arg2 = desiredNo-(Math.floor((Math.sqrt(4*desiredNo+1)-1)/2)*Math.floor((Math.sqrt(4*desiredNo+1)-1)/2))-Math.floor((Math.sqrt(4*desiredNo+1)-1)/2);
var OptimalLeftOver = Math.min( arg1 ,arg2 );
//We add two rectangles per row: one at the beginning one at the end
//we start with the row below the first one
var line;
var pozy;
var pozx1, pozx2;
var nRectLine_i;
if(OptimalLeftOver===arg1){
line=1;//1st line is skipped
pozy=RectangleDim;
pozx1 = 0;
while(OptimalLeftOver>0) {
nRectLine_i = 2* Math.floor(Math.sqrt(desiredNo))-1-2*line;
pozx2 = (line-1)*RectangleDim+RectangleDim*(nRectLine_i+1);//pozx1+nRectLine_i+2*RectangleDim;
$scope.rectangles.push($scope.DrawRect(pozx1,pozy));
OptimalLeftOver-=1;
if(OptimalLeftOver>0) {
$scope.rectangles.push($scope.DrawRect(pozx2, pozy));
OptimalLeftOver -= 1;
}
//document.getElementById("val").innerHTML = teste;
line += 1;
pozy+=RectangleDim;
pozx1=RectangleDim*line - RectangleDim;
}
//document.getElementById('tablePrint').innerHTML = finalTable;
}
else {
line=1;//1st line is skipped
pozy=RectangleDim;
pozx1 = 0;
while(OptimalLeftOver>0) {
nRectLine_i = 2* Math.floor(Math.sqrt(desiredNo))-2*line;
pozx2 = RectangleDim*(line-1)+RectangleDim*(nRectLine_i+1);//pozx1+nRectLine_i+2*RectangleDim;
$scope.rectangles.push($scope.DrawRect(pozx1,pozy));
OptimalLeftOver-=1;
if(OptimalLeftOver>0) {
$scope.rectangles.push($scope.DrawRect(pozx2, pozy));
OptimalLeftOver -= 1;
}
//document.getElementById("val").innerHTML = teste;
line += 1;
pozy+=RectangleDim;
pozx1=RectangleDim*line - RectangleDim;
}
//document.getElementById('tablePrint').innerHTML = finalTable;
}
};
/* $scope.DrawMatrix=function(desiredNo)
{
/* Chooses optimal leftover number based on the progression formulas.
Attempts to minimize the work of the designer of the response form without
making too much assumptions
desiredNo = document.getElementById("NumQuest").value;
var arg1 = desiredNo - ( Math.floor(Math.sqrt(desiredNo))*Math.floor(Math.sqrt(desiredNo)));
var arg2 = desiredNo - (Math.floor((Math.sqrt(4*desiredNo+1)-1)/2)*Math.floor((Math.sqrt(4*desiredNo+1)-1)/2))-Math.floor((Math.sqrt(4*desiredNo+1)-1)/2);
var OptimalLeftOver = Math.min( arg1 ,arg2 );
document.getElementById("val").innerHTML = 'There are '+OptimalLeftOver+' questions missing!'+ arg1+ '___'+arg2;
console.log(arg1);
if(OptimalLeftOver===arg1){
DrawMatrixPerfectProgression(desiredNo);
AddExtraRectangles(desiredNo);
}
else {
DrawMatrixEvenProgression(desiredNo);
AddExtraRectangles(desiredNo);
}
}; */
}
);
angular.bootstrap(document.getElementById('body'), ["app"]);
The relevant part of the code is the $scope.rectangles array which contains the JSON.parse of the strings representing my data structure on the html side and that structure in JSON (or JSON parsed or whatever) is what I want to save in the MongoDB database...How can I do that? The HTML relevant part is just like this:
HTML:
<p><button ng-click="DrawMatrixEvenProgression(NumQuest)">Draw</button></p>
<svg ng-attr-height="{{graph.height}}" ng-attr-width="{{graph.width}}">
<rect ng-repeat="rect in rectangles"
ng-attr-x="{{rect.x}}"
ng-attr-y="{{rect.y}}"
ng-attr-width="{{rect.width}}"
ng-attr-height="{{rect.height}}">
</rect>
</svg>
Any help will be appreciated... Can I start by adding more files to that project to handle the database and then things will be linked together?
Like adding stuff to handle the mongoose and the connections?
Thanks in advance!
Because Angular is a front-end framework. So to communicate with database (in this case MongoDB) you need to have application on the server-side to handle this and I suggest you to use Node.js and Mongoose as a MongoDB driver.
Node.js
Mongoose
Come back to Angular, you can create Angular service or factory and let the them talk to your server with service like $http or $resource.
Angular service documentation
Example for angular service
angular.module('app')
.factory('RectangleService', function($http){
return {
create: create
}
function create(rectangle){
// make http request to the server
return $http({
url: 'API_URL',
method: 'GET',
params: rectangle
});
}
});
After you create your service you have to inject it to your controller and
you may create some function to your $scope to talk with service like this
app.controller('MainCtrl', function($scope, RectangleService) { // <-- Inject service to controller
// your controller code
$scope.createRectangle = function(rectangle){
RectangleService.create(rectangle);
}
});
You can map createRectangle function to directive like ng-click and pass your json data as a parameter
Because I don't know what server-side language you can use, so I don't come with an example for Node.js & Mongoose
Hope this can help :)

Export multiple html tables to Excel

I've scavenged the inter web for answers and though I found some, they were mostly incomplete or not working.
What I'm trying to do is: I have a info page which displays information about a customer or server (or something else), this information is displayed in a table, sometimes multiple tables (I sometimes create my own table for some of the data and use Html.Grid(Model.list) to create tables for the rest of the data stored in lists, all on 1 page).
I found this website which is an awesome: http://www.excelmashup.com/ and does exactly what I want for 1 table, though I need this for multiple tables (they must all be in the same Excel file). I know I can create multiple files (1 for each table) but this is not the desired output.
So I kept on searching and I found a post on stackoverflow: Export multiple HTML tables to Excel with JavaScript function
This seemed promising so I tried using it but the code had some minor errors which I tried to fix:
var tableToExcel = (function () {
var uri = 'data:application/vnd.ms-excel;base64,'
, template = '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40"><head><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>{worksheet}</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--></head><body><table>{table}</table></body></html>'
, base64 = function (s) { return window.btoa(unescape(encodeURIComponent(s))) }
, format = function (s, c) { return s.replace(/{(\w+)}/g, function (m, p) { return c[p]; }) }
return function (table, name) {
if (!table.nodeType) table = document.getElementById(table)
var ctx = { worksheet: name || 'Worksheet', table: table.innerHTML }
window.location.href = uri + base64(format(template, ctx))
}
})()
The button I use to trigger it:
<input type="button" onclick="tableToExcel('InformatieTable', 'W3C Example Table')" value="Export to Excel">
but alas to no avail (I did not know what to do with the if (!table.nodeType) table = table line so I just commented it since it seemed to do nothing special).
Now I get an error, or well not really an error but this is what it says when I try to run this code:
Resource interpreted as Document but transferred with MIME type application/vnd.ms-excel: "data:application/vnd.ms-excel;base64,PGh0bWwgeG1sbnM6bz0idXJuOnNjaGVtYXMtbW…JzZXQ9VVRGLTgiLz48L2hlYWQ+PGJvZHk+PHRhYmxlPjwvdGFibGU+PC9ib2R5PjwvaHRtbD4=".
And I get an Excel file as download in my browser but when I try to open it I get an error about the content and file extension not matching and if I would still like to open it. So if I click ok it opens a empty Excel sheet and that's it.
I am currently trying to fix that error, though i don't think it will make any difference to the content of the Excel file.
Is there anyone that can help me fix this? Or provide an other way of doing this?
I do prefer it to be run client side (so jQuery/java) instead of server side to minimize server load.
EDIT
I've found a better example of the jQuery (one that does work) on http://www.codeproject.com/Tips/755203/Export-HTML-table-to-Excel-With-CSS
This converts 1 table into an excel file which is obviously not good enough. But now I have the code to do this so I should be able to adapt it to loop trough all tables on the web page.
Also updated the code in this example to the correct version I'm using now.
I also still get the same error yet when I click on ok when trying to open the Excel file it does show me the content of the table, so I'm just ignoring that for now. anyone who has a solution for this please share.
Thanks to #Axel Richter I got my answer, he reffered me to the following question
I have adapted the code a bit so it would Take all the tables on the web page so it now looks like this:
<script type="text/javascript">
var tablesToExcel = (function () {
var uri = 'data:application/vnd.ms-excel;base64,'
, tmplWorkbookXML = '<?xml version="1.0"?><?mso-application progid="Excel.Sheet"?><Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">'
+ '<DocumentProperties xmlns="urn:schemas-microsoft-com:office:office"><Author>Axel Richter</Author><Created>{created}</Created></DocumentProperties>'
+ '<Styles>'
+ '<Style ss:ID="Currency"><NumberFormat ss:Format="Currency"></NumberFormat></Style>'
+ '<Style ss:ID="Date"><NumberFormat ss:Format="Medium Date"></NumberFormat></Style>'
+ '</Styles>'
+ '{worksheets}</Workbook>'
, tmplWorksheetXML = '<Worksheet ss:Name="{nameWS}"><Table>{rows}</Table></Worksheet>'
, tmplCellXML = '<Cell{attributeStyleID}{attributeFormula}><Data ss:Type="{nameType}">{data}</Data></Cell>'
, base64 = function (s) { return window.btoa(unescape(encodeURIComponent(s))) }
, format = function (s, c) { return s.replace(/{(\w+)}/g, function (m, p) { return c[p]; }) }
return function (wsnames, wbname, appname) {
var ctx = "";
var workbookXML = "";
var worksheetsXML = "";
var rowsXML = "";
var tables = $('table');
for (var i = 0; i < tables.length; i++) {
for (var j = 0; j < tables[i].rows.length; j++) {
rowsXML += '<Row>'
for (var k = 0; k < tables[i].rows[j].cells.length; k++) {
var dataType = tables[i].rows[j].cells[k].getAttribute("data-type");
var dataStyle = tables[i].rows[j].cells[k].getAttribute("data-style");
var dataValue = tables[i].rows[j].cells[k].getAttribute("data-value");
dataValue = (dataValue) ? dataValue : tables[i].rows[j].cells[k].innerHTML;
var dataFormula = tables[i].rows[j].cells[k].getAttribute("data-formula");
dataFormula = (dataFormula) ? dataFormula : (appname == 'Calc' && dataType == 'DateTime') ? dataValue : null;
ctx = {
attributeStyleID: (dataStyle == 'Currency' || dataStyle == 'Date') ? ' ss:StyleID="' + dataStyle + '"' : ''
, nameType: (dataType == 'Number' || dataType == 'DateTime' || dataType == 'Boolean' || dataType == 'Error') ? dataType : 'String'
, data: (dataFormula) ? '' : dataValue.replace('<br>', '')
, attributeFormula: (dataFormula) ? ' ss:Formula="' + dataFormula + '"' : ''
};
rowsXML += format(tmplCellXML, ctx);
}
rowsXML += '</Row>'
}
ctx = { rows: rowsXML, nameWS: wsnames[i] || 'Sheet' + i };
worksheetsXML += format(tmplWorksheetXML, ctx);
rowsXML = "";
}
ctx = { created: (new Date()).getTime(), worksheets: worksheetsXML };
workbookXML = format(tmplWorkbookXML, ctx);
console.log(workbookXML);
var link = document.createElement("A");
link.href = uri + base64(workbookXML);
link.download = wbname || 'Workbook.xls';
link.target = '_blank';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
})();
</script>
so now when ever I want a page to have an option to be exported to excel i add a refference to that script and i add the following button to my page:
<button onclick="tablesToExcel(['ServerInformatie', 'Relaties'], 'VirtueleMachineInfo.xls', 'Excel')">Export to Excel</button>
so the method:
tablesToExcel(WorksheetNames, fileName, 'Excel')
Where worksheetNames is an array which needs to contain as much names (or more) as there are tables on the page. You could ofcourse chose to create the worksheet names in a different way.
And where fileName is ofcourse the name of the file you'll be downloading.
Not having it all in 1 worksheet is a shame but at least this will do for now.
Here is the code that I used to put multiple HTML tables in the same Excel sheet:
import TableExport from 'tableexport';
const tbOptions = {
formats: ["xlsx"], // (String[]), filetype(s) for the export, (default: ['xlsx', 'csv', 'txt'])
bootstrap: true, // (Boolean), style buttons using bootstrap, (default: true)
exportButtons: false, // (Boolean), automatically generate the built-in export buttons for each of the specified formats (default: true)
position: "bottom", // (top, bottom), position of the caption element relative to table, (default: 'bottom')
}
DowlandExcel = (key) => {
const table = TableExport(document.getElementById(key), tbOptions);
var exportData = table.getExportData();
var xlsxData = exportData[key].xlsx;
console.log(xlsxData); // Replace with the kind of file you want from the exportData
table.export2file(xlsxData.data, xlsxData.mimeType, xlsxData.filename, xlsxData.fileExtension, xlsxData.merges, xlsxData.RTL, xlsxData.sheetname)
}
DowlandExcelMultiTable = (keys) => {
const tables = []
const xlsxDatas = []
keys.forEach(key => {
const selector = document.getElementById(key);
if (selector) {
const table = TableExport(selector, tbOptions);
tables.push(table);
xlsxDatas.push(table.getExportData()[key].xlsx)
}
});
const mergeXlsxData = {
RTL: false,
data: [],
fileExtension: ".xlsx",
filename: 'rapor',
merges: [],
mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
sheetname: "Rapor"
}
for (let i = 0; i < xlsxDatas.length; i++) {
const xlsxData = xlsxDatas[i];
mergeXlsxData.data.push(...xlsxData.data)
xlsxData.merges = xlsxData.merges.map(merge => {
const diff = mergeXlsxData.data.length - xlsxData.data.length;
merge.e.r += diff;
merge.s.r += diff;
return merge
});
mergeXlsxData.merges.push(...xlsxData.merges)
mergeXlsxData.data.push([null]);
}
console.log(mergeXlsxData);
tables[0].export2file(mergeXlsxData.data, mergeXlsxData.mimeType, mergeXlsxData.filename, mergeXlsxData.fileExtension, mergeXlsxData.merges, mergeXlsxData.RTL, mergeXlsxData.sheetname)
}