I have made a table on one SharePoint page , and would like others to edit on that table. however, whenever they add the edits, and I refresh my page, the inputs are gone. I used contenteditable for this. What could be an efficient way to do this? I don't need to save the previous input that were on the table.
The data on the page that has not been saved to the database or not hard-coded will disappear after refresh.
You could create a SharePoint List to save the contents users edited and use Rest API to get item and create new item.
Sample code:
<style>
.output {
font: 1rem 'Fira Sans', sans-serif;
}
blockquote {
background: #eee;
border-radius: 5px;
margin: 16px 0;
}
blockquote p {
padding: 15px;
}
cite {
margin: 16px 32px;
}
blockquote p::before {
content: '\201C';
}
blockquote p::after {
content: '\201D';
}
[contenteditable='true'] {
caret-color: red;
}
</style>
<blockquote contenteditable="true">
<p id="content">Edit this content to add your own quote</p>
<input type="button" value="finish edit" id="submit1" />
</blockquote>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script>
$(function () {
getListData();
$("#submit1").click(function(){
CreateListItemWithDetails('Contents',$("#content").text())
})
});
function getListData() {
var fullUrl = _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getByTitle('Contents')/items";
$.ajax({
url: fullUrl,
type: "GET",
headers: {
"accept": "application/json;odata=verbose",
"content-type": "application/json;odata=verbose",
},
success: onQuerySucceeded,
error: function onQueryFailed() {
alert('Error!');
}
});
}
function onQuerySucceeded(data) {
if(data.d.results.length>0){
var index=data.d.results.length - 1;
$("#content").text(data.d.results[index].Title)
}
}
function CreateListItemWithDetails(listName, newItemTitle) {
var itemType = GetItemTypeForListName(listName);
var item = {
"__metadata": { "type": itemType },
"Title": newItemTitle
};
$.ajax({
url: _spPageContextInfo.siteAbsoluteUrl + "/_api/web/lists/getByTitle('"+listName+"')/items",
type: "POST",
contentType: "application/json;odata=verbose",
data: JSON.stringify(item),
headers: {
"Accept": "application/json;odata=verbose",
"X-RequestDigest": $("#__REQUESTDIGEST").val()
},
success: function (data) {
console.log("success");
},
error: function (data) {
console.log("fail");
}
});
}
// Get List Item Type metadata
function GetItemTypeForListName(name) {
return "SP.Data." + name.charAt(0).toUpperCase() + name.split(" ").join("").slice(1) + "ListItem";
}
</script>
I just use p element to give an example and you need to change the list title to yours. The edited content will only be saved to SharePoint after clicking the finish edit button .
Related
I am a beginner in asp.net MVC
I have tblBlock,tblFloor,tblRoom table in my database. I want to display all these in a single page like when I click Block number it should show the list of the floor and when I click on floor number it should show the number of rooms. how to do this within a single page in asp.net MVC.
This my view code
function GetFloors() {
$("#tblFloor tbody tr").remove();
console.log("Hello Javascript");
$.ajax({
type: "GET",
//url: "/RoomBooking/GetFloors",
url: '#Url.Action("GetFloors","RoomBooking")',
dataType: "json",
//cache: false,
async: "false",
contenttype:"charset=utf-8",
success: function (data) {
$.each(data, function (i, item) {
var rows = ""
+ ""
+ ' tblFloor ' + item.Floor_No + ""
+ "";
$('#tblFloor tbody').append(rows);
});
},
});
//Prevent default behavior
return false;
}
This is my controller
[HttpGet]
public JsonResult GetFloors()
{
List<tblFloor> floors = new List<tblFloor>();
floors = BlockRepsitory.GetFloors(1).ToList();
return Json(floors, JsonRequestBehavior.AllowGet);
}
Finally, I got a solution to this problem below is code for that solution:
<script>
$(document).ready(function () {
loadData();
});
function loadData() {
$.ajax({
url: "/PeopleBooking/Blocklist",
type: "GET",
contentType: "application/json;charset=utf-8",
dataType: "json",
success: function (result) {
var html = '';
$.each(result, function (key, item) {
html += '<div class="col-md-3 col-lg-3 col-xl-2 "><section class="panel panel-featured-left panel-featured-primary "><div class="panel-body zoom"><div class="widget-summary"><div class="widget-summary-col widget-summary-col-icon"><div class="summary-icon bg-white"><img src="/assets/images/RoomImg/block.png" width = "150" height = "150" class="img-circle img-responsive"/></div></div><div class="widget-summary-col"><div class="summary"><h4 class="title">' + item.Block_name + '</h4><div class="info"><strong class="amount">' + item.Block_No + '</strong></div></div></div></div></div></div></section></div>';
});
$('#yes').html(html);
},
error: function (errormessage) {
alert(errormessage.responseText);
}
});
}
</script>
I have a React project in which part of the functionality involves sending a request to a Nodejs server that runs PhantomJS and the html-pdf plugin in order to create a pdf that gets build via a react component from my node server. The problem I'm having is actually embedding custom fonts into my react component. I have a base64 encoded font set that would be easier to include versus a bunch of files, but due to how React loads in CSS, I keep getting errors.
Here is the function that gets called to generate the PDF on my NodeJS server:
exports.order = (req, res, next) => {
phantom.create().then(function(ph) {
ph.createPage().then(function(page) {
// page.viewportSize = { width: 600, height: 600 };
page.open(`http://localhost:3005/print/work/${req.params.id}`).then(function(status) {
page.property('content').then(function(content) {
pdf.create(content, options).toBuffer(function(err, buffer){
res.writeHead(200, {
'Content-Type': 'application/pdf',
'Content-Disposition': `attachment; filename=order-${req.params.id}.pdf`,
'Content-Length': buffer.length
});
res.end(buffer);
});
page.close();
ph.exit();
});
});
});
});
}
Here's the actual React component that contains the HTML for the PDF:
var React = require('react');
var moment = require('moment');
class PrintWorkOrder extends React.Component {
render() {
var order = this.props;
var s = {
body: {
width: '100%',
float: 'none',
fontFamily: 'Helvetica',
color: '#000',
display: 'block',
fontSize: 10,
}
}
return (
<div style={s.body}>
I shortened the content in her for brevity
</div>
)
}
}
module.exports = PrintWorkOrder;
The functionality currently works, it's just now I need to add some custom fonts that aren't Helvetica and I'm having trouble solving that with this current implementation. Does anyone have any insight into this?
I also have the same issue, what I did is to encoded front into base64 and put it as
font.css
#font-face {
font-family: 'fontName';
src: url(data:application/x-font-woff;charset=utf-8;base64,<base64>) format('woff'),
font-weight: normal;
font-style: normal;
}
The trick is to read CSS file content and inject it into the DOM markup as style tag
React.createElement('style', { dangerouslySetInnerHTML: { __html: this.props.children } });
So to do it:
For the second line of code, what it does it to create style element. You could do is to write a component that read content from the .css file(s) and then put the content inside style element like this:
Style.js
var React = require('react');
export default class Style extends React.Component {
static propTypes = {
children: React.PropTypes.string.isRequired
};
render() {
return React.createElement('style', { dangerouslySetInnerHTML: { __html: this.props.children } });
}
}
Modify PrintWorkOrder.js
var React = require('react');
var moment = require('moment');
var fs = require('fs');
var path = require('path');
var Style = require('./Style');
const readFile = (filePath) => fs.readFileSync(path.resolve(__dirname, filePath)).toString();
class PrintWorkOrder extends React.Component {
constructor(props) {
super(props);
this.state = {
styles: []
}
}
componentWillMount() {
this.setState({
styles: [
readFile('font.css') // Assuming putting font.css same location with PrintWorkOrder component
]
})
}
render() {
var order = this.props;
var s = {
body: {
width: '100%',
float: 'none',
fontFamily: 'Helvetica',
color: '#000',
display: 'block',
fontSize: 10,
}
}
return (
<html>
<head>
{/* Here to render all the styles */}
{this.state.styles.map(s => (<Style>{s}</Style>))}
</head>
<body>
<div style={s.body}>
I shortened the content in her for brevity
</div>
</body>
</html>
);
}
}
module.exports = PrintWorkOrder;
I have no idea what I'm doing wrong, but I cannot get typeahead working in my MVC 5 application. I installed everything via NuGet and my view includes #Scripts.Render("~/bundles/typeahead"), which is rendering properly when viewing the source of the view. So the issue isn't that the dependencies are missing.
I am not seeing any drop down appear when I start typing, and using Fiddler I do not see any calls being made out to the remote that I setup that pulls the data.
Here's the line in my view that typeahead is being attached:
#Html.TextBoxFor(m => m.MainInfo.CompanyName,
new { #class = "form-control typeahead", id = "comp-name", autocomplete="off" })
Here's the portion of my script that configures typeahead and bloodhound:
$(document).ready(function() {
var clients = new Bloodhound({
datumTokenizer: function (datum) {
return Bloodhound.tokenizers.whitespace(datum.value);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: "/info/client?like=%QUERY",
wildcard: '%QUERY',
filter: function (clients) {
return $.map(clients, function (client) {
return {
value: client.Name,
clientId: client.Identifier
};
});
}
}
});
clients.initialize();
$('#comp-name').typeahead(null,
{
display: 'value',
minLength: 1,
source: clients.ttAdapter(),
templates: {
empty: "Looks like a new client...",
suggestion: Handlebars.compile("<p><b>{{value}}</b> - {{clientId}}</p>")
}
});
});
Is there something that I've configured wrong in my javascript? I've used a few tutorials as well as their own documentation, but I cannot figure out what I'm doing wrong here. It almost feels like it's not properly initialized, but there are no errors being thrown.
NOTE: Just as an FYI I'm using Bootstrap 3 as well in case that changes anything.
EDIT: Here's my #section Scripts:
#Scripts.Render("~/bundles/jqueryval")
#Scripts.Render("~/bundles/typeahead")
<script src="#Url.Content("~/Scripts/handlebars.min.js")"></script>
<script src="#Url.Content("~/Scripts/ProjectSetupFormScripts.js")"></script> <-- this is where typeahead is set up
This did the trick for me:
JS
#section Scripts {
<script type="text/javascript">
$(function () {
SetupTipeahead();
});
function SetupTipeahead() {
var engine = new Bloodhound({
remote: {
url: '/Employees/AllEmployees',
ajax: {
type: 'GET'
}
},
datumTokenizer: function (d) {
return Bloodhound.tokenizers.whitespace(d.FullName);
},
queryTokenizer: Bloodhound.tokenizers.whitespace
});
engine.initialize();
$('#FullName').typeahead(null, {
displayKey: 'FullName',
source: engine.ttAdapter(),
templates: {
empty: [
'<div class="empty-message">',
'No match',
'</div>'
].join('\n'),
suggestion: function (data) {
return '<p class="">' + data.FullName + '</p><p class="">' + data.ManNumber + '</p>';
}
}
});
}
</script>
EmployeesController has the following JsonResult
public JsonResult AllEmployees()
{
return Json(db.Employees.ToList(),JsonRequestBehavior.AllowGet);
}
Hello try to wrap your script in #section scripts {} this will place the script at the bottom just before the </body> tag and make sure you are not calling the function before your bundles load.
#section scripts {
<script>
$(document).ready(function() {
var clients = new Bloodhound({
datumTokenizer: function (datum) {
return Bloodhound.tokenizers.whitespace(datum.value);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: "/info/client?like=%QUERY",
wildcard: '%QUERY',
filter: function (clients) {
return $.map(clients, function (client) {
return {
value: client.Name,
clientId: client.Identifier
};
});
}
}
});
clients.initialize();
$('#comp-name').typeahead(null,
{
display: 'value',
minLength: 1,
source: clients.ttAdapter(),
templates: {
empty: "Looks like a new client...",
suggestion: Handlebars.compile("<p><b>{{value}}</b> - {{clientId}}</p>")
}
});
});
</script>
}
I successfully created an upload field in create mode in jQuery jTable as follows:
upload: {
title: 'Upload Image',
input: function (data) {
return '<input type="file" name="file">';
'<input type="submit" value="Submit" id="submitBtn" />';
},
},
I am able to successfully display the browse button and select files. I am unable to submit the form:
Error: newcourse.php' not found or unable to stat.
with the same file name in which the code is.
I am at a dead end. Where will the file be uploaded to? In the same directory?
How to do it in jTable ? Can it be done using ajax ? If so, how to proceed? jTable documentation is very poor.
I finally found a soluton to upload files on jTable
Now with the version of jtable 2.4.0 (the most recent at the moment writing this post)
on your file.js add the following methods:
// Read a page's GET URL variables and return them as an associative array.
function getVars(url)
{
var formData = new FormData();
var split;
$.each(url.split("&"), function(key, value) {
split = value.split("=");
formData.append(split[0], decodeURIComponent(split[1].replace(/\+/g, " ")));
});
return formData;
}
// Variable to store your files
var files;
$( document ).delegate('#input-image','change', prepareUpload);
// Grab the files and set them to our variable
function prepareUpload(event)
{
files = event.target.files;
}
//The actions for your table:
$('#table-container').jtable({
actions: {
listAction: 'backoffice/catalogs/display',
deleteAction: 'backoffice/catalogs/delete',
createAction: function (postData) {
var formData = getVars(postData);
if($('#input-image').val() !== ""){
formData.append("userfile", $('#input-image').get(0).files[0]);
}
return $.Deferred(function ($dfd) {
$.ajax({
url: 'backoffice/catalogs/update',
type: 'POST',
dataType: 'json',
data: formData,
processData: false, // Don't process the files
contentType: false, // Set content type to false as jQuery will tell the server its a query string request
success: function (data) {
$dfd.resolve(data);
$('#table-container').jtable('load');
},
error: function () {
$dfd.reject();
}
});
});
},
updateAction: function (postData) {
var formData = getVars(postData);
if($('#input-image').val() !== ""){
formData.append("userfile", $('#input-image').get(0).files[0]);
}
return $.Deferred(function ($dfd) {
$.ajax({
url: 'backoffice/catalogs/update',
type: 'POST',
dataType: 'json',
data: formData,
processData: false, // Don't process the files
contentType: false, // Set content type to false as jQuery will tell the server its a query string request
success: function (data) {
$dfd.resolve(data);
$('#table-container').jtable('load');
},
error: function () {
$dfd.reject();
}
});
});
},
// Now for the fields:
fields: {
id_catalog: {
key: true,
create: false,
edit: false,
list: false
},
thumb_url: {
title: 'Image',
type: 'file',
create: false,
edit: true,
list: true,
display: function(data){
return '<img src="' + data.record.thumb_url + '" width="150" height="207" class="preview">';
},
input: function(data){
return '<img src="' + data.record.thumb_url + '" width="150" height="207" class="preview">';
},
width: "150",
listClass: "class-row-center"
},
image: {
title: 'Select File',
list: false,
create: true,
input: function(data) {
html = '<input type ="file" id="input-image" name="userfile" accept="image/*" />';
return html;
}
}
}
});
Now, you are able to process the files on your server side.
That's all folks.
Hello shels
I lost a lot of time to searching of a solution of this issue. I read a many articles and found that this solution working for me fine:
actions: {
listAction: 'modules_data.php?action=list',
deleteAction: 'modules_data.php?action=delete',
updateAction: 'modules_data.php?action=update',
createAction: 'modules_data.php?action=create'
},
...
image_file: {
title: 'Album cover',
list: true,
create: true,
edit: true,
input: function(data) {
return '<form target="iframeTarget" class="formUploadFile" action="file_upload.php" method="post" enctype="multipart/form-data"> <input type="file" onchange="this.form.submit()" name="myFile"/> </form> <iframe class="upload-iframe" style="display: none;" src="#" name="iframeTarget"></iframe>';
}
},
image: {
title: 'Album cover',
list: true,
create: true,
edit: true,
input: function(data) {
html = '<input type ="hidden" id="image" name="image" />';
return html;
}
},
....
How to capture submit response in a form created dynamically?
So you can capture submit event using:
...
formSubmitting: function(event, data) {
filename = $('input[type=file]').val().split('\\').pop();
($("#" + data.form.attr("id")).find('input[name="image"]').val(filename));
},
And save data to the server side script.
I think, it's a good solution and hope will be helpful.
Best Regards
Kameliya
console.log not working for FormData. Use instead
for (var data of formData) {
console.log(data);
}
I'm trying create custom footers such in phantomjs examples: https://github.com/ariya/phantomjs/blob/master/examples/printheaderfooter.js
Here is my code:
var phantom = require('node-phantom');
phantom.create(function (err, ph) {
ph.createPage(function (err, page) {
page.set('paperSize', {
format: 'A4',
orientation: 'portrait',
footer: {
contents: ph.callback(function (pageNum, numPages) {
if (pageNum == 1) {
return "";
}
return "<h1>Header <span style='float:right'>" + pageNum + " / " + numPages + "</span></h1>";
})
}
}, function () {
page.open('http://www.google.com', function () {
})
})
})
});
But unfortunately I get the following error:
TypeError: Object #<Object> has no method 'callback';
Is it bug that ph does not expose callback method?
There are two problems in your script :
ph is not the classic phantom object, but a proxy object. node-phantom use web sockets to invoke phantomjs. Of course, some features are lost using this implementation.
functions are not serialized when calling page.set
Printing custom header/footer also requires to call phantom.callback. This method is not documented and so not exposed by node-phantom (and can't be). We need to find a way to apply this method in this package.
There are many solutions. Here is my possible solution :
Serialize your functions in a string in your script
var phantom = require('node-phantom');
phantom.create(function (err, ph) {
ph.createPage(function (err, page) {
page.set('paperSize', {
format: 'A4',
orientation: 'portrait',
header: {
height: "1cm",
contents: 'function(pageNum, numPages) { return pageNum + "/" + numPages; }'
},
footer: {
height: "1cm",
contents: 'function(pageNum, numPages) { return pageNum + "/" + numPages; }'
}
}, function () {
page.open('http://www.google.fr', function () {
page.render('google.pdf');
ph.exit();
})
})
})
});
edit bridge.js and add phantom.callback + eval. This allow us to re-plug the header/footer .contents.
case 'pageSet':
eval('request[4].header.contents = phantom.callback('+request[4].header.contents+')');
eval('request[4].footer.contents = phantom.callback('+request[4].footer.contents+')');
page[request[3]]=request[4];
respond([id,cmdId,'pageSetDone']);
break;
As you can see this works ! (Google in French)
Unfortunately, node-phantom doesn't appear to support phantom.callback. Since the project is inactive for more than a year, I think it's unlikely to be updated in the near future.
On the other hand, phantomjs-node supports phantom.callback() since version 0.6.6. You can use it like this:
var phantom = require('phantom');
phantom.create(function (ph) {
ph.createPage(function (page) {
page.open("http://www.google.com", function (status) {
var paperConfig = {
format: 'A4',
orientation: 'portrait',
border: '1cm',
header: {
height: '1cm',
contents: ph.callback(function(pageNum, numPages) {
return '<h1>My Custom Header</h1>';
})
},
footer: {
height: '1cm',
contents: ph.callback(function(pageNum, numPages) {
return '<p>Page ' + pageNum + ' / ' + numPages + '</p>';
})
}
};
page.set('paperSize', paperConfig, function() {
// render to pdf
page.render('path/to/file.pdf', function() {
page.close();
ph.exit();
});
});
});
});
});
As you can also see on this gist.
node phantom seems to expose this proxy-object via the create function (this should be your ph-object):
var proxy={
createPage:function(callback){
request(socket,[0,'createPage'],callbackOrDummy(callback));
},
injectJs:function(filename,callback){
request(socket,[0,'injectJs',filename],callbackOrDummy(callback));
},
addCookie: function(cookie, callback){
request(socket,[0,'addCookie', cookie],callbackOrDummy(callback));
},
exit:function(callback){
request(socket,[0,'exit'],callbackOrDummy(callback));
},
on: function(){
phantom.on.apply(phantom, arguments);
},
_phantom: phantom
};
that means, that you can probably acces the phantoms callback like this:
ph._phantom.callback
Here what I did to access phantom.callback:
add this to node-phantom.js line 202:
callback: function(callback){
request(socket,[0,'callback'],callbackOrDummy(callback));
},
just before _phantom: phantom
and add this to bridge.js line 45:
case 'callback':
phantom.callback(request[3]);
break;
Hope it helps!