I am trying to develop my very first Tableau extension for Tableau server. I am developing it locally.
It is almost a Hello World kind of extension.
When I try to add an extension, it throws me an error "Request Rejected By Server"
It is a node.js app and running perfectly fine.
Here is my server.js
const express = require('express');
const app = express();
const process = require('process');
app.get('/', async function(req, res){
res.sendFile(__dirname + "/public/datasources.html");
});
const listener = app.listen(3030, () =>{
console.log("App is listening on port " + listener.address().port);
Here is my datasources.html.. This is one of those provided as sample from tableau.. I just added js part of it at the end. All in one file
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Datasources Sample</title>
<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<!-- Bootstrap -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" >
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" ></script>
<!-- Extensions Library (this will be hosted on a CDN eventually) -->
<script src="https://extensions.tableauusercontent.com/resources/tableau.extensions.1.latest.min.js"></script>
<!-- Our extension's code -->
</head>
<body>
<div class="container">
<!-- DataSources Table -->
<div id="dataSources">
<h4>All DataSources</h4>
<div class="table-responsive">
<table id="loading" class="table">
<tbody><tr><td>Loading...</td></tr></tbody>
</table>
<table id="dataSourcesTable" class="table table-striped hidden">
<thead>
<tr>
<th>DataSource Name</th>
<th>Auto Refresh</th>
<th style="width: 100%">Info</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<!-- More dataSource info modal -->
<div class="modal fade" id="infoModal" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title">DataSource Details</h4>
</div>
<div id="dataSourceDetails" class="modal-body">
<div class="table-responsive">
<table id="detailsTable" class="table">
<tbody>
<tr>
<td>DataSource Name</td>
<td id="nameDetail"></td>
</tr>
<tr>
<td>DataSource Id</td>
<td id="idDetail"></td>
</tr>
<tr>
<td>Type</td>
<td id="typeDetail"></td>
</tr>
<tr>
<td>Fields</td>
<td id="fieldsDetail"></td>
</tr>
<tr>
<td>Connections</td>
<td id="connectionsDetail"></td>
</tr>
<tr>
<td>Active Tables</td>
<td id="activeTablesDetail"></td>
</tr>
</tbody>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// Wrap everything in an anonymous function to avoid polluting the global namespace
(function () {
$(document).ready(function () {
tableau.extensions.initializeAsync().then(function () {
// Since dataSource info is attached to the worksheet, we will perform
// one async call per worksheet to get every dataSource used in this
// dashboard. This demonstrates the use of Promise.all to combine
// promises together and wait for each of them to resolve.
let dataSourceFetchPromises = [];
// Maps dataSource id to dataSource so we can keep track of unique dataSources.
let dashboardDataSources = {};
// To get dataSource info, first get the dashboard.
const dashboard = tableau.extensions.dashboardContent.dashboard;
// Then loop through each worksheet and get its dataSources, save promise for later.
dashboard.worksheets.forEach(function (worksheet) {
dataSourceFetchPromises.push(worksheet.getDataSourcesAsync());
});
Promise.all(dataSourceFetchPromises).then(function (fetchResults) {
fetchResults.forEach(function (dataSourcesForWorksheet) {
dataSourcesForWorksheet.forEach(function (dataSource) {
if (!dashboardDataSources[dataSource.id]) { // We've already seen it, skip it.
dashboardDataSources[dataSource.id] = dataSource;
}
});
});
buildDataSourcesTable(dashboardDataSources);
// This just modifies the UI by removing the loading banner and showing the dataSources table.
$('#loading').addClass('hidden');
$('#dataSourcesTable').removeClass('hidden').addClass('show');
});
}, function (err) {
// Something went wrong in initialization.
console.log('Error while Initializing: ' + err.toString());
});
});
// Refreshes the given dataSource.
function refreshDataSource (dataSource) {
dataSource.refreshAsync().then(function () {
console.log(dataSource.name + ': Refreshed Successfully');
});
}
// Displays a modal dialog with more details about the given dataSource.
function showModal (dataSource) {
let modal = $('#infoModal');
$('#nameDetail').text(dataSource.name);
$('#idDetail').text(dataSource.id);
$('#typeDetail').text((dataSource.isExtract) ? 'Extract' : 'Live');
// Loop through every field in the dataSource and concat it to a string.
let fieldNamesStr = '';
dataSource.fields.forEach(function (field) {
fieldNamesStr += field.name + ', ';
});
// Slice off the last ", " for formatting.
$('#fieldsDetail').text(fieldNamesStr.slice(0, -2));
dataSource.getConnectionSummariesAsync().then(function (connectionSummaries) {
// Loop through each connection summary and list the connection's
// name and type in the info field
let connectionsStr = '';
connectionSummaries.forEach(function (summary) {
connectionsStr += summary.name + ': ' + summary.type + ', ';
});
// Slice of the last ", " for formatting.
$('#connectionsDetail').text(connectionsStr.slice(0, -2));
});
dataSource.getActiveTablesAsync().then(function (activeTables) {
// Loop through each table that was used in creating this datasource
let tableStr = '';
activeTables.forEach(function (table) {
tableStr += table.name + ', ';
});
// Slice of the last ", " for formatting.
$('#activeTablesDetail').text(tableStr.slice(0, -2));
});
modal.modal('show');
}
// Constructs UI that displays all the dataSources in this dashboard
// given a mapping from dataSourceId to dataSource objects.
function buildDataSourcesTable (dataSources) {
// Clear the table first.
$('#dataSourcesTable > tbody tr').remove();
const dataSourcesTable = $('#dataSourcesTable > tbody')[0];
// Add an entry to the dataSources table for each dataSource.
for (let dataSourceId in dataSources) {
const dataSource = dataSources[dataSourceId];
let newRow = dataSourcesTable.insertRow(dataSourcesTable.rows.length);
let nameCell = newRow.insertCell(0);
let refreshCell = newRow.insertCell(1);
let infoCell = newRow.insertCell(2);
let refreshButton = document.createElement('button');
refreshButton.innerHTML = ('Refresh Now');
refreshButton.type = 'button';
refreshButton.className = 'btn btn-primary';
refreshButton.addEventListener('click', function () { refreshDataSource(dataSource); });
let infoSpan = document.createElement('span');
infoSpan.className = 'glyphicon glyphicon-info-sign';
infoSpan.addEventListener('click', function () { showModal(dataSource); });
nameCell.innerHTML = dataSource.name;
refreshCell.appendChild(refreshButton);
infoCell.appendChild(infoSpan);
}
}
})();
</script>
</body>
</html>
And here is my trex extension file.
<?xml version="1.0" encoding="utf-8"?>
<manifest manifest-version="0.1" xmlns="http://www.tableau.com/xml/extension_manifest">
<dashboard-extension id="com.tableau.extensions.samples.datasources" extension-version="0.6.0">
<default-locale>en_US</default-locale>
<name resource-id="name"/>
<description>DataSources Sample</description>
<author name="tableau" email="github#tableau.com" organization="tableau" website="https://www.tableau.com"/>
<min-api-version>0.8</min-api-version>
<source-location>
<url>http://MACHINE_NAME:3030/</url>
</source-location>
<icon>iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAlhJREFUOI2Nkt9vy1EYh5/3bbsvRSySCZbIxI+ZCKsN2TKtSFyIrV2WuRCJuBiJWxfuxCVXbvwFgiEtposgLFJElnbU1SxIZIIRJDKTrdu+53Uhra4mce7Oe57Pcz7JOULFisViwZ+29LAzOSjQYDgz1ZcCvWuXV11MJpN+OS/lm6179teqH0yDqxPTCyKSA8DcDsyOmOprnCaeP7459pdgy969i0LTC3IO/RQMyoHcQN+3cnljW3dNIFC47qDaK3g7BwdTkwBaBELT4ZPOUVWgKl4ZBnjxJPUlMDnTDrp0pmr6RHFeEjjcUUXPDGeSEwDN0Xg8sivxMhJNjGzbHd8PkM3eHRfkrBM5NkcQaY2vUnTlrDIA0NoaX+KLXFFlowr14tvVpqb2MICzmQcKqxvbumv+NAhZGCCIPwEw6QWXKYRL/VUXO0+rAUJiPwAk5MIlgVfwPjjHLCL1APmHN94ZdqeYN+NW/mn6I4BvwQYchcLnwFhJMDiYmlRxAzjpKWZkYkUCcZ2I61wi37tLbYyjiN0fHk5Oz3nGSLSzBbNHCF35R7f6K1/hN9PRhek11FrymfQQQKB4+Gl05P2qNRtmETlXW7e+b2z01dfycGNbfFMAbqNyKp9Jp4rzOT8RYFs0njJkc2iqsCObvTsOsDWWqA5C1uFy+Uz/oXJeKwVT4h0RmPUXhi79vuC0Ku6yOffTK3g9lfxfDQAisY516sg5kfOCiJk7HoLt2cf9b/9LANAc7dznm98PagG1fUOZ9IP5uMB8Q4CPoyNvausapkTt3rNMuvdf3C/o6+czhtdwmwAAAABJRU5ErkJggg==</icon>
<permissions>
<permission>full data</permission>
</permissions>
</dashboard-extension>
<resources>
<resource id="name">
<text locale="en_US">DataSources Sample</text>
</resource>
</resources>
</manifest>
Do I have to do something on Tableau server side to make it work?
This might be because you are using http:// without localhost. That is not allowed. I'm not sure why the error message is so unhelpful, but it could be that the error coming back from server is not formatted well for the client error box. Hard to know without logs. :)
Try out either switching to hosting your extension from http://localhost or create a self-signed certificate with SAN, add that to your trust store and host from https:// instead.
Related
I am making a todolist app using express, mongoose, mongodb, bootstrap. When I hit my update button to update a task it just makes a duplicate of the task that i'm trying to update. How would I go about making my update button update the original task?
Here are some screenshots on what happens when I try to update :
https://i.stack.imgur.com/t11OG.jpg - here I created a task "make breakfast".
https://i.stack.imgur.com/ijt98.jpg - here I hit the yellow update button and I am updating the task from "make breakfast" to "make lunch".
https://i.stack.imgur.com/V4RCe.jpg - here when I hit the green update button it creates a separate task instead of updating the original "make breakfast" task.
My routes and my ejs for the home page are below:
I can also show the ejs for updating a task as well.
Thanks
const express = require("express");
const { route } = require("express/lib/application");
const router = express.Router();
const mongoose = require("mongoose");
const Todoinfo = require("../models/infoSchema");
router.get("/", async (req, res) => {
// get all todos
const allTodos = await Todoinfo.find({}).sort("-date");
res.render("home", { allTodos });
});
router.post("/", async (req, res) => {
// add a todo
const newTodo = new Todoinfo(req.body); // create a new todo
await newTodo.save((err) => {
// save the new todo
if (err) {
res.send("Not updated");
}
});
//res.redirect("/");
});
router.get("/:id/delete", async (req, res) => {
// delete a todo
const todoDelete = await Todoinfo.findByIdAndDelete(req.params.id); // find the todo by id and delete it?
res.redirect("/"); // redirect to home page
});
router.get("/:id/finish", async (req, res) => {
// finish a todo (change status to completed)
const todoDelete = await Todoinfo.findByIdAndUpdate(req.params.id, {
progress: "Completed",
});
res.redirect("/");
});
router.get("/:id/update", async (req, res) => {
// update a todo (change status to in progress)
const updateTodo = await Todoinfo.findById(req.params.id); // find the todo by id and update it?
res.render("update", { updateTodo }); // render the update page with the todo
});
router.get("/:id/update/final", async (req, res) => {
// update a todo (change status to finished)
const updateTodo = await Todoinfo.findByIdAndUpdate(req.params.id, {
// find the todo by id and update it?
description: req.body.description, // update the description of the todo with the new description
});
res.redirect("/");
});
module.exports = router;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap#5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<title>Todo App</title>
</head>
<body>
<section class="vh-100" style="background-color: #eee ">
<div class="container py-5 h-100">
<div class="row d-flex justify-content-center align-items-center h-100">
<div class="col col-lg-12 col-xl-g">
<div class="card rounded-3">
<div class="card-body p-4">
<h4 class="text-center my-3 pb-3 text-primary ">My Todo App</h4>
<form class="row row-cols-lg-auto g-3 justify-content-center
align-items-center mb-4 pb-2" action="/" method="POST">
<div class="col-12">
<div class="form-outline">
<input type="text" id="form1" class="form-control" name="description" />
<label class="form-label" for="form1">Create a new task </label>
</div>
</div>
<div class="col-12">
<button type="submit" class="btn btn-primary mb-4">Add</button>
</div>
</form>
<table class="table mb-4">
<thead>
<tr>
<th scope="col">Item No.</th>
<th scope="col">Todo Item</th>
<th scope="col">Action</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<% for(let i= 0; i <allTodos.length; i++) { %>
<tr>
<% let z= i + 1 %>
<th scope="row"><%= z %></th>
<% if (! allTodos[i].progress.localeCompare(" Completed" )) { %>
<td class="text-decoration-line-through"> <%=
allTodos[i].description %> </td>
<%} else {%>
<td> <%= allTodos[i].description %> </td>
<%}%>
<td> <%= allTodos[i].progress %> </td>
<td>
<button type="submit" class="btn btn-danger ms-1 mb-1">Delete</button>
<% if (! allTodos[i].progress.localeCompare(" Completed"))
{ %>
<%} else {%>
<a href="/<%= allTodos[i]._id %>/finish" class="text-decoration-none">
<button type="submit" class="btn btn-success ms-1 mb-1">Finished</button></a>
<button type="submit" class="btn btn-warning ms-1 mb-1">Update</button>
<% } %>
</td>
</tr>
<% } %>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</section>
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
</body>
</html>
I am facing the problem in redirecting to index page. Instead page refreshes to same create page perhaps due to large size images are still copying but why page refreshing instead of images waiting copying to finish and then redirecting.
module.exports.create_customModel_post = async (req, res) => {
const {images} = req.files
let myPromise = []
if (images) {
const dir = `./public/${req.body.slug}`
fs.mkdirSync(dir, { recursive: true });
for (let i = 0; i < images.length; i++) {
const uploadTo = `${dir}/${images[i].name}`
myPromise.push(new Promise(function(resolve, reject) {
images[i].mv(uploadTo, (err) => {
if(err){
reject(err);
}else{
resolve(uploadTo);
}
});
}));
}
}
Promise.all(myPromise).then(function(values) {
console.log(values),
// THIS REDIRECT IS NOT WORKING
res.redirect('/admin/custom-models')
}).catch(function (reasons) {
console.log(reasons);
});
}
Response from server
POST /admin/create-custom-model - - ms - -
new model is created & saved
[
'./public/testing/attachment_48549925.svg',
'./public/testing/brand-name.png',
'./public/testing/design.svg',
'./public/testing/model-name.png',
'./public/testing/path9080.png',
]
GET /admin/create-custom-model 200 12.375 ms - -
here is form I am using. Removed extra
<main>
<section class="">
<div class="container">
<h1>Create Custom Model</h1>
<table class="">
<form enctype="multipart/form-data" method="post">
<tr>
<td></td>
<td>
<input type="file" name="images" multiple>
</td>
</tr>
<tr>
<td> </td>
</tr>
<tr>
<td>Cancel </td>
<td><button type="submit" >Save</button></td>
</tr>
</form>
</table>
</div>
</section>
</main>
i want to read this txt-file using datatables.net
https://www.rapidtech1898.com/aaadownload/arrays.txt
But when i want to use the file with the following files the output is not working i have this error in the chrome inspector:
(datatables is not reading as it sould and the inspector shows me that)
(at first i had the txt-file locally and read that there are some problems with that using chrome with local file - but this is a "normal" http-link isn´t it? - why is this stil not working as expected?
I also tried to do the same thing locally before - but i get the same error:
I have an index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.25/css/jquery.dataTables.min.css">
</head>
<body>
<div class="container py-5">
<header class="text-center text-white">
<h1 class="display-4">Levermann Scores</h1>
</header>
<div class="row py-5">
<div class="col-lg-10 mx-auto">
<div class="card rounded shadow border-0">
<div class="card-body p-5 bg-white rounded">
<div class="table-responsive">
<table id="example" class="display" style="width:100%">
<thead>
<tr>
<th>Name</th>
<th>Position</th>
<th>Office</th>
<th>Extn.</th>
<th>Start date</th>
<th>Salary</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Name</th>
<th>Position</th>
<th>Office</th>
<th>Extn.</th>
<th>Start date</th>
<th>Salary</th>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript" charset="utf8" src="https://code.jquery.com/jquery-3.5.1.js"></script>
<script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/1.10.25/js/jquery.dataTables.min.js"></script>
<script type="text/javascript" src="mainAJAX.js"></script>
</body>
</html>
And this is the mainAJAX.js file:
$(document).ready(function() {
$('#example').DataTable( {
// "ajax": "http://localhost:2121/arrays.txt"
"ajax": "https://www.rapidtech1898.com/aaadownload/arrays.txt"
} );
} );
Somebody told me - that if also the server is hosted on the same domain (like rapidtech1898.com) it would probably work. But is there no way to test such think locally before deploying this somewhere else?
For reasons of security, that is how CORS works. You cannot request data from a different origin unless the server allows it. However, for the purpose of development, you can disable CORS in multiple ways. There is a nice article for it here. But my personal favourite is this solution (to use a proxy that doesn't bypasses CORS):
fetch('https://cors-anywhere.herokuapp.com/https://www.rapidtech1898.com/aaadownload/arrays.txt', {
method: "GET",
headers: {
"Content-type": "application/json;charset=UTF-8",
"x-requested-with": "localhost:3000"
}
}).then(response => response.json())
.then(json => console.log(json))
.catch(err => console.log(err));
I think i found the problem -
In the server-js i have to usse express.static for the data-folder:
const express = require("express");
const app = express();
app.use(express.static('data'))
app.listen(2121, () => {
console.log("Server is running on port 2121...");
});
And in the datafolder i have to put all files (index.html, mainAJAX.js and arrays.txt).
Then i can call the text-file in the mainAJAX.js like that:
$(document).ready(function() {
$('#example').DataTable( {
"ajax": "arrays.txt"
} );
} );
I am looking for a solution for my EJS template. I am working on a project and I have completed about 50% of it. Currently I am working on a web page where I have to display the SQL data into EJS web template.
I have posted my coding which is i am working on it.
data is receiving from database (I checked with console.log and I posted the Json out put).
I tried all the possible ways to work it out but I did not get the result. It would be great if someone could help me..
Thanks in advance.
/* app.js */
app.get('/data', receiveData);
function receiveData(req, res)
{
db.executeSql("SELECT * FROM arduino", function (recordsets, err, ) {
var data = JSON.stringify(recordsets);
if (err) {
httpMsgs.show500(request, res, err);
}
else {
var Jdata = JSON.parse(data);
res.render('arduino',{Jdata:Jdata});
console.log(data);
}
});
}
/* arduino.ejs */
<html>
<head>
<body>
<div class="page-data">
<div class="data-table">
<table border="1" cellpadding="7" cellspacing="7">
<tr>
<th> - </th>
<th>ID</th>
<th>Machine</th>
<th>Start Time</th>
<th>End Time</th>
<th>Length Time</th>
<th> Day/Night</th>
<th>Job Number</th>
</tr>
<% if(Jdata.length){
for(var i = 0;i < Jdata.length;i++) { %>
<tr>
<td><%=(i+1)%></td>
<td> </td>
<td><%=Jdata[i].Machine%></td>
<td><%=Jdata[i].StartTime%></td>
<td><%=Jdata[i].EndTime%></td>
<td><%=Jdata[i].LengthTime%></td>
<td><%=Jdata[i].Day%></td>
<td><%=Jdata[i].ID %></td>
<td><%=Jdata[i].JobNumber %></td>
</tr>
<% }
}else{ %>
<tr>
<td colspan="3">No Data</td>
</tr>
<% } %>
</table>
</div>
</div>
</body>
</head>
</html>
{"recordsets":[[{"ID":1,"Machine":"TRUMPF 5000","StartTime":"2018-11-01T15:28:51.000Z","EndTime":"2018-11-01T15:52:11.000Z","LengthTime":271,"Day":"Day","JobNumber":null}]]
I'm currently teaching myself the ins and outs of building a MEAN app. I started with the basic todo app and modified it pretty heavily to support multiple key value pairs and have updated the UI using bootstrap.
It's currently hosted here: http://surveymanager-30817.onmodulus.net/
I've implemented ui-sortable, and it works perfectly by itself.
The challenge I'm having, that I cannot seem to find any relevant documentation or tutorials for - is how to communicate the updated sort order to mongo so when I refresh the page, ng-repeat will repeat question in questions with the order that I had previously created.
Here is the HTML
<!-- index.html -->
<!doctype html>
<!-- ASSIGN OUR ANGULAR MODULE -->
<html ng-app="QuestionManager">
<head>
<!-- META -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"><!-- Optimize mobile viewport -->
<title>Question Manager</title>
<!-- SCROLLS -->
<link href="css/bootstrap.min.css" rel="stylesheet">
<link href="css/question-bootstrap.css" rel="stylesheet">
<style>
html { overflow-y:scroll; }
body { padding-top:30px; }
#todo-list { margin-bottom:30px; }
</style>
<!-- SPELLS -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script><!-- load jquery -->
<script src="//code.jquery.com/ui/1.9.1/jquery-ui.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js"></script><!-- load angular -->
<script src="js/sortable.js"></script>
<script src="core.js"></script>
</head>
<!-- SET THE CONTROLLER AND GET ALL TODOS -->
<body ng-controller="mainController">
<div class="container sm_head">
<div class="col-sm-6">
</div>
<div class="col-sm-6">
<h2 class="pull-right">Survey Manager</h2>
</div>
</div>
<div class="container">
<!-- Nav tabs -->
<ul id="navtabs" class="nav nav-tabs" role="tablist">
<li class="active">Manage</li>
<li>Create</li>
<li>Render Code</li>
<li>About</li>
</ul>
<!-- Tab panes -->
<div class="tab-content">
<div class="tab-pane active" id="manage">
<div class="panel panel-default">
<!-- Default panel contents -->
<div class="panel-heading">Manage Question Order<span class="badge pull-right">{{ questions.length }} Questions</span></div>
<!-- Table -->
<table class="table">
<thead>
<tr>
<th>Order</th>
<th>Question Name</th>
<th>Evergage Field</th>
<th>Username</th>
<th>Options</th>
</tr>
</thead>
<tbody ui-sortable="sortableOptions" ng-model="questions">
<tr ng-repeat="question in questions">
<td>{{ question.order }}</td>
<td>{{ question.meanName }}</td>
<td>{{ question.fieldName }}</td>
<td>#mdo</td>
<td>
<button type="button" class="btn btn-default btn-sm" ng-click="deleteQuestion(question._id)">
<span class="ques-list glyphicon glyphicon-remove"></span> delete </button>
</td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-8">
<form class="form-horizontal">
<fieldset>
<!-- Form Name -->
<legend>Question Details</legend>
<!-- Text input-->
<div class="form-group">
<label class="col-md-4 control-label" for="Question Name">Question Order</label>
<div class="col-md-8">
<input id="Question Order" name="Question Order" type="text" placeholder="Question Order" class="form-control input-md" ng-model="formData.order">
</div>
</div>
<!-- Text input-->
<div class="form-group">
<label class="col-md-4 control-label" for="Question Name">Question Name</label>
<div class="col-md-8">
<input id="Question Name" name="Question Name" type="text" placeholder="Write something meaningful" class="form-control input-md" ng-model="formData.meanName">
</div>
</div>
<!-- Text input-->
<div class="form-group">
<label class="col-md-4 control-label" for="Custom Field Name">Custom Field</label>
<div class="col-md-8">
<input id="Custom Field Name" name="Custom Field Name" type="text" placeholder="Format: User.Profile.xx.xx.xx ( 1 or 3 additional words)" class="form-control input-md" ng-model="formData.fieldName">
</div>
</div>
<!-- Button -->
<div class="form-group">
<label class="col-md-4 control-label" for="Create"></label>
<div class="col-md-4">
<button id="Create" name="Create" class="btn btn-primary" ng-click="createQuestion()">Create</button>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<div class="tab-pane" id="create">
</div>
<div class="tab-pane" id="render">...</div>
<div class="tab-pane" id="about">...</div>
</div>
</div>
<script src="js/bootstrap.min.js"></script>
</body>
</html>
Here is the client side:
// public/core.js
var QuestionManager = angular.module('QuestionManager', ['ui.sortable']);
function mainController($scope, $http) {
$scope.formData = {};
// when landing on the page, get all questions and show them
$http.get('/api/questions')
.success(function(data) {
$scope.questions = data;
console.log(data);
})
.error(function(data) {
console.log('Error: ' + data);
});
// when submitting the add form, send the text to the node API
$scope.createQuestion = function() {
$http.post('/api/questions', $scope.formData)
.success(function(data) {
$scope.formData = {};
console.log('fuck you!');
$scope.questions = data;
console.log(data);
})
.error(function(data) {
console.log('Error: ' + data);
});
};
// delete a question after checking it
$scope.deleteQuestion = function(id) {
$http.delete('/api/questions/' + id)
.success(function(data) {
$scope.questions = data;
console.log(data);
})
.error(function(data) {
console.log('Error: ' + data);
});
};
$scope.$watch("questions", function(newVal, oldVal) {
console.log("oldval", oldVal);
console.log("newVal", newVal);
});
$scope.sortableOptions = {
update: function(e, ui) {
$http.put('/api/questions', $scope.questions)
console.log($scope.questions);
},
axis: 'y'
};
}
// Some Bootstrap Initializers
$('#navtabs>li a').click(function (e) {
e.preventDefault()
$(this).tab('show')
})
And here is the server side with my random test and console logs starting at line 44
// server.js
var express = require('express');
var app = express(); // create our app w/ express
var mongoose = require('mongoose'); // mongoose for mongodb
var morgan = require('morgan'); // log requests to the console (express4)
var bodyParser = require('body-parser'); // pull information from HTML POST (express4)
var methodOverride = require('method-override'); // simulate DELETE and PUT (express4)
mongoose.connect('mongodb://ewill3532:12qwaszx#proximus.modulusmongo.net:27017/pUxo2hir'); // connect to mongoDB database locally
app.use(express.static(__dirname + '/public')); // set the static files location /public/img will be /img for users
app.use(morgan('dev')); // log every request to the console
app.use(bodyParser.urlencoded({'extended':'true'})); // parse application/x-www-form-urlencoded
app.use(bodyParser.json()); // parse application/json
app.use(bodyParser.json({ type: 'application/vnd.api+json' })); // parse application/vnd.api+json as json
app.use(methodOverride());
var question = mongoose.model('question', {
order : String,
fieldName : String,
meanName : String
});
app.get('/api/questions', function(req, res) {
// use mongoose to get all questions in the database
question.find(function(err, questions) {
// if there is an error retrieving, send the error. nothing after res.send(err) will execute
if (err)
res.send(err)
res.json(questions); // return all questions in JSON format
});
});
// create todo and send back all questions after creation
app.post('/api/questions', function(req, res) {
// create a todo, information comes from AJAX request from Angular
question.create({
order : req.body.order,
fieldName : req.body.fieldName,
meanName : req.body.meanName,
done : false
}, function(err, todo) {
if (err)
res.send(err);
// get and return all the questions after you create another
question.find(function(err, questions) {
if (err)
res.send(err)
res.json(questions);
});
});
});
// delete a todo
app.delete('/api/questions/:todo_id', function(req, res) {
question.remove({
_id : req.params.todo_id
}, function(err, todo) {
if (err)
res.send(err);
// get and return all the questions after you create another
question.find(function(err, questions) {
if (err)
res.send(err)
res.json(questions);
});
});
});
// let's just put this here so that it comes back as found
app.put('/api/questions', function(req, res) {
});
app.get('*', function(req, res) {
res.sendfile('./public/index.html'); // load the single view file (angular will handle the page changes on the front-end)
});
// listen (start app with node server.js) ======================================
app.listen(8080);
console.log("App listening on port 8080");
Any help here, or if someone can point me to some good documentation or tutorials - either way would be very helpful!'
Thanks!