I am creating a Yeoman Generator to automate creation of few database tables. I need to provide users with a prompt to add multiple columns (combination of ColumnName and DataType below).
I have a template saved in my disk where I bind the dynamic names from the user inputs and based upon this template, the final script is generated by the Yeoman Generator. Can you suggest how to prompt user to enter repetitive combinations of ColumnName/DataType he wants to enter?
var prompts = [{
type: 'input',
name: 'name',
message: 'The Table Name?'
}, {
type: 'input',
name: 'attributeName',
message: 'Define your Schema - ColumnName?',
default: 'ID'
},{
type: 'input',
name: 'attributeType',
message: 'Define your Schema - DataType?',
default: 'S'
}
];
return this.prompt(prompts).then(function (props) {
this.props = props;
}.bind(this));
Template content --
User can enter details of 1/2/3/4 or more columns and once he does that the template below should be intelligent enough to create that many column key combinations.
{
"Type" : "AWS::Table",
"Properties" : {
"AttributeDefinitions" :
{
"AttributeName" : "<%= attributeName %>",
"AttributeType" : "<%= attributeType %>"
},
{
"AttributeName" : "<%= attributeName %>",
"AttributeType" : "<%= attributeType %>"
},
"TableName" : <%= name %>,
}
}
You can add a recursive function inside the prompting() hook. It has to be made sure that the recursive function returns this.prompts else the execution might stop.
My strategy works like below
Declare a recursive function that repeats based on one of the input
Populate the props as you recurse through in this.columns
Pass this instance variable to the template in writing() hook
Iterate over the this.columns in the template and populate your columns
First entry in this.columns will have the table name and the first column details
Check the code below, you can tweak this to your needs as long as the recursive function is invoked as expected.
There is an extra prompt that asks whether to repeat or not. It can also be discarded with some logic but that is up to you.
prompting()
prompting() {
// Have Yeoman greet the user.
this.log(yosay(
'Welcome to the remarkable ' + chalk.red('generator-react-starter-kit-relay-container') + ' generator!'
));
const tableNamePrompt = [{
type: 'input',
name: 'name',
message: 'The Table Name?'
}];
const columnPrompts = [{
type: 'input',
name: 'attributeName',
message: 'Define your Schema - ColumnName?',
default: 'ID'
}, {
type: 'input',
name: 'attributeType',
message: 'Define your Schema - DataType?',
default: 'S'
}, {
type: 'confirm',
name: 'repeat',
message: 'Do you want to add more columns?',
default: 'Y'
}]
this.columns = [];
const loop = (relevantPrompts) => {
return this.prompt(relevantPrompts).then(props => {
this.columns.push(props);
return props.repeat ? loop(columnPrompts) : this.prompt([]);
})
}
return loop([...tableNamePrompt, ...columnPrompts]);
}
And then in the writing() hook pass the columns instance variable that you populated earlier.
writing()
writing() {
this.fs.copyTpl(
this.templatePath('Schema.json'),
this.destinationPath('./Schema.json'),
{
columns: this.columns
}
);
}
TEMPLATE
{
"Type" : "AWS::Table",
"Properties" : {
"AttributeDefinitions" : [
<% for (let i=0; i<columns.length; i++) { %>
{
"AttributeName": "<%= columns[i].attributeName %>",
"AttributeType": "<%= columns[i].attributeType %>"
}
<% } %>
],
"TableName" : "<%= (columns[0] || {}).name %>"
}
}
SAMPLE INPUT
_-----_ ╭──────────────────────────╮
| | │ Welcome to the │
|--(o)--| │ remarkable │
`---------´ │ generator-react-starter- │
( _´U`_ ) │ kit-relay-container │
/___A___\ /│ generator! │
| ~ | ╰──────────────────────────╯
__'.___.'__
´ ` |° ´ Y `
? The Table Name? User
? Define your Schema - ColumnName? ID
? Define your Schema - DataType? Bigint
? Do you want to add more columns? Yes
? Define your Schema - ColumnName? Email
? Define your Schema - DataType? String
? Do you want to add more columns? Yes
? Define your Schema - ColumnName? Password
? Define your Schema - DataType? Text
? Do you want to add more columns? No
OUTPUT
{
"Type" : "AWS::Table",
"Properties" : {
"AttributeDefinitions" : [
{
"AttributeName": "ID",
"AttributeType": "Bigint"
}
{
"AttributeName": "Email",
"AttributeType": "String"
}
{
"AttributeName": "Password",
"AttributeType": "Text"
}
],
"TableName" : "User"
}
}
Related
My Rails website display a simple table about name and age of students.
name age
Lily 25
Tom 27
Chris 19
...
So I have #names = Student.pluck(:name), #ages = Student.pluck(:age). Now I would like to generate a line chart by using Highcharts:
HTML: <div id='students-chart'></div>
JavaScript:
$(function() {
Highcharts.chart('students_chart', {
...
};
};
Now I should provide the name and age to the chart as the xAxis and yAxis. The simplest way is to include the JavaScript in the html.erb file and provide the data by <%= #names %> and <%= #ages %>. However, it's not recommended, and I want to put the JavaScript code in the assets/javascripts/students.js file.
A very common way to fetch the data in the JavaScript file is using the Ajax, however, my data is already in the page so I don't want to add an extra action in the controller to send the data.
So what's the best practice to get the data for the Highcharts? data- attribute?
No front-end frameworks in the project, only jQuery. I know some gems could help me like Chartkick or LazyHighCharts, but I would like to know the basic strategy.
This is one way to show the chart, just jQuery getting data from the controller.
In controller fetch the data, adjust and convert to json. Customise respect to on your models. Here is an example with an array of hashes (data are passed as arrays):
#series = [ {name: 'Lily', data: [25]}, {name: 'Tom', data: [27]}, {name: 'Chris', data: [19]} ].to_json
For example, if your User model includes the age column, you can adjust like this:
#series = User.all.map{ |user| {name: user.name, data: [user.age]} }.to_json
In view (customise as you will), passing the variable here:
<div id='students_chart'></div>
<script>
$(function () {
var myChart = Highcharts.chart('students_chart', {
chart: {
type: 'column'
},
title: {
text: 'User ages'
},
xAxis: {
categories: ['Users']
},
yAxis: {
title: {
text: 'Age'
}
},
series: <%= raw #series %>
});
});
</script>
Edit - get data from server
Instead of sending data to view, render as json (no need to add e new action):
respond_to do |format|
format.html
format.json { render json: #series }
end
Then place the javascript in a file and get json data using jQuery.getJSON():
$.getJSON(window.location.href, function(json) {
var highChartData = json;
console.log(json)
var myChart = Highcharts.chart('students_chart', {
chart: {
type: 'column'
},
title: {
text: 'User ages'
},
xAxis: {
categories: ['Users']
},
yAxis: {
title: {
text: 'Age'
}
},
series: highChartData
});
});
I have an array of objects in my controller eg:
$scope.fields = [
{fieldName:'houseNum',fieldLabel:'House Number',disabled:false},
{fieldName:'street',fieldLabel:'Street',disabled:false},
{fieldName:'city',fieldLabel:'City',disabled:true},
{fieldName:'state',fieldLabel:'State',disabled:true},
]
In the HTML I would like to be able to get a fieldLabel where fieldName=='street'. The AJS documentation presumes that every filter case should be in the context of ng-repeat - but not so in my case as I am just trying to pluck one 'fieldLabel' from the 'fields' array based on 'fieldName'
eg: HTML
{{ fieldLabel in fields | filter : {fieldName:'street'} : true}}
How can I make something like this work - or do I need to create my own directive and pass the $scope.fields to the directive and loop through manually?
You could do:
{{ (fields | filter : {fieldName:"street"} : true)[0].fieldLabel}}
(fields | filter : {fieldName:"street"} : true) returns an array of filtered items get the first one [0] and access fieldLabel property out of that object.
angular.module('app', []).controller('ctrl', function($scope) {
$scope.fields = [{
fieldName: 'houseNum',
fieldLabel: 'House Number',
disabled: false
}, {
fieldName: 'street',
fieldLabel: 'Street',
disabled: false
}, {
fieldName: 'city',
fieldLabel: 'City',
disabled: true
}, {
fieldName: 'state',
fieldLabel: 'State',
disabled: true
}, ]
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
{{ (fields | filter : {fieldName:"street"} : true)[0].fieldLabel}}
</div>
Though better option would be to set the property from the controller itself, so that the filter does not run during every digest cycle.
function getFieldByName(prop){
var field = {};
//Or just use a for loop and break once you find a match
$scope.fields.some(function(itm){
if(itm.fieldName === prop){
field = itm;
return true;
}
});
//Or you could inject $filter as well an do as below
//return $filter('filter')($scope.fields,{fieldName:"street"})[0] || {}
return field;
}
//Somewhere
$scope.streetField = getFieldByName('street');
In the view:
{{streetField.fieldLabel}}
Array.some
I am trying to build a contact form which is very similar to the one used in the keystone demo but i have hit a road block, While trying to save to db, I get the following errors
{ message: 'Validation failed',
name: 'ValidationError',
errors:
{ name:
{ name: 'ValidatorError',
path: 'name',
message: 'Name is required',
type: 'required' } } }
I have checked the fields on the form and also the request in the backend by doing a console.log but for some reason i still keep on getting the same error.
Here is what I have in my jade file
section#contact-container
section#contact.contact-us
.container
.section-header
// SECTION TITLE
h2.white-text Get in touch
// SHORT DESCRIPTION ABOUT THE SECTION
h6.white-text
| Have any question? Drop us a message. We will get back to you in 24 hours.
if enquirySubmitted
.row
h3.white-text.wow.fadeInLeft.animated(data-wow-offset='30', data-wow-duration='1.5s', data-wow-delay='0.15s') Thanks for getting in touch.
else
.row
form#contact.contact-form(method="post")
input(type='hidden', name='action', value='contact')
.wow.fadeInLeft.animated(data-wow-offset='30', data-wow-duration='1.5s', data-wow-delay='0.15s')
.col-lg-4.col-sm-4(class=validationErrors.name ? 'has-error' : null)
input.form-control.input-box(type='text', name='name', value=formData.name, placeholder='Your Name')
.col-lg-4.col-sm-4
input.form-control.input-box(type='email', name='email', value=formData.email, placeholder='Your Email')
.col-lg-4.col-sm-4
div(class=validationErrors.enquiryType ? 'has-error' : null)
input.form-control.input-box(type='text', name='enquiryType', placeholder='Subject', value=formData.enquiryType)
.col-md-12(class=validationErrors.message ? 'has-error' : null)
.col-md-12.wow.fadeInRight.animated(data-wow-offset='30', data-wow-duration='1.5s', data-wow-delay='0.15s')
textarea.form-control.textarea-box(name='message', placeholder='Your Message')= formData.message
button.btn.btn-primary.custom-button.red-btn.wow.fadeInLeft.animated(data-wow-offset='30', data-wow-duration='1.5s', data-wow-delay='0.15s', type='submit') Send Message
and this is how my schema and route file looks like
var keystone = require('keystone'),
Types = keystone.Field.Types;
var Enquiry = new keystone.List('Enquiry', {
nocreate: true,
noedit: true
});
Enquiry.add({
name: { type: Types.Name, required: true },
email: { type: Types.Email, required: true },
enquiryType: { type: String },
message: { type: Types.Markdown, required: true },
createdAt: { type: Date, default: Date.now }
});
Enquiry.schema.pre('save', function(next) {
this.wasNew = this.isNew;
next();
});
Enquiry.schema.post('save', function() {
if (this.wasNew) {
this.sendNotificationEmail();
}
});
Enquiry.schema.methods.sendNotificationEmail = function(callback) {
var enqiury = this;
keystone.list('User').model.find().where('isAdmin', true).exec(function(err, admins) {
if (err) return callback(err);
new keystone.Email('enquiry-notification').send({
to: admins,
from: {
name: 'Wheatcroft Accounting',
email: 'contact#abc.com'
},
subject: 'New Enquiry for **',
enquiry: enqiury
}, callback);
});
};
Enquiry.defaultSort = '-createdAt';
Enquiry.defaultColumns = 'name, email, enquiryType, createdAt';
Enquiry.register();
This is the route file
var keystone = require('keystone'),
async = require('async'),
Enquiry = keystone.list('Enquiry');
exports = module.exports = function(req, res) {
var view = new keystone.View(req, res),
locals = res.locals;
locals.section = 'contact';
locals.formData = req.body || {};
locals.validationErrors = {};
locals.enquirySubmitted = false;
view.on('post', { action: 'contact' }, function(next) {
var newEnquiry = new Enquiry.model();
var updater = newEnquiry.getUpdateHandler(req);
updater.process(req.body, {
flashErrors: true,
fields: 'name, email, enquiryType, message',
errorMessage: 'There was a problem submitting your enquiry:'
}, function(err) {
if (err) {
locals.validationErrors = err.errors;
console.log(err);
} else {
locals.enquirySubmitted = true;
}
next();
});
});
view.render('contact');
}
I believe the problem has to do with the way KeystoneJS handles Types.Name field types internally.
In your jade file, you should reference your name field using a path to its virtual name.full property. Internally name.full has a setter that splits the name into name.first and name.last. So, if you wish to have separate input for the first and last name you should use name.first and name.last. If you want a single input to enter the full name you should use name.full.
Try replacing the input control for your name field:
input.form-control.input-box(type='text', name='name', value=formData.name, placeholder='Your Name')
with this:
input.form-control.input-box(type='text', name='name.full', value=formData['name.full'])
I’m just beginning with mean.js and getting my feet wet building out a custom article module with a few basic extra properties. I’m trying to add a list of tags that are added on the create form via an text input field. As you can see below, this works fine when they're freshly created. I can see the tags array in the newly created document in the terminal. When I load the edit view the tags field is populated with the elements of the tags array separated by commas. That seems ok. Now I want to simply add new tags after the current ones, separating each new tag with a comma, and have the array updated with the new list. However, after performing the update the tags array elements change to one long string containing all tags.
I had a look at the server side update function but I’m not quite sure what to do there to make it work. It appears the article is being updated as a complete object so maybe I need to extract the tags array and perform a push new tags separately?Anyone know what I’m doing wrong?I've been troubleshooting it for a day or so now and am out of ideas.Thanks in advance!
JSON print of new article:
{
"user" : ObjectId("545db575197ad8a949894a18"),
"desc" : “some description”,
"_id" : ObjectId("546115f20a3048862033d393"),
"created" : ISODate("2014-11-10T19:45:54.079Z"),
"tags" : [
"here”,
"be",
"tags"
],
"name" : "Test”,
"__v" : 0
}
JSON print after update - Note: tags are now one string
{
"__v" : 1,
"_id" : ObjectId("546115f20a3048862033d393"),
"created" : ISODate("2014-11-10T19:45:54.079Z"),
"desc" : "some description",
"name" : "Test",
"tags" : [
"here,be,tags,more"
],
"user" : ObjectId("545db575197ad8a949894a18")
}
// Create new article
$scope.create = function() {
console.log('Tags before split: ' + this.tags);
var tags = this.tags;
var tags = tags.split(" ");
console.log(“Tags array “ + tags);
// Create new article object
var article = new Article ({
name: this.name,
desc: this.desc,
tags: tags
});
// Update existing article
$scope.update = function() {
var article = $scope.article;
console.log(“This "
+ article);
article.$update(function() {
$location.path('articles/' + article._id);
}, function(errorResponse) {
$scope.error = errorResponse.data.message;
});
};
Tags input field in create-article view
<div class="form-group">
<label class="control-label" for="tags">Tags</label>
<div class="controls">
<input type="text" data-ng-model="tags" id="tags" class="form-control" placeholder="Tags" required>
</div>
</div>
Tags input field in update-article view
<div class="form-group">
<label class="control-label" for="tags">Tags</label>
<div class="controls">
<input type="text" data-ng-model="article.tags" id="tags" class="form-control" placeholder="Tags" required>
</div>
</div>
Update article server side controller
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
errorHandler = require('./errors.server.controller'),
Article = mongoose.model('Article'),
_ = require('lodash');
exports.update = function(req, res) {
var article = req.article;
article = _.extend(article , req.body);
article.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.jsonp(article);
}
});
};
The articles model
var ArticleSchema = new Schema({
name: {
type: String,
default: '',
required: 'Please fill Article name',
trim: true
},
desc: String,
tags: [String],
created: {
type: Date,
default: Date.now
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
mongoose.model('Article', ArticleSchema);
You need to split the tags before you update, just the way you do in your create function.
I'm currently testing the Kendo UI MVC Extensions Beta.
I'm trying to implement a double click - edit but I don't know how I can get the rowId.
JavaScript:
$('#GridPedidos table tr').live('dblclick', function () {
alert(' grid dbl clicked');
});
View:
#(Html.Kendo().Grid(Model) _
.Name("GridPedidos") _
.Columns(Sub(column)
column.Bound(Function(item) item.idPedidoDocumentacao).Width("5%")
column.Bound(Function(item) item.descEstadoPedidoDoc).Width("25%")
column.Bound(Function(item) item.descTipoPedidoDoc).Width("25%")
column.Bound(Function(item) item.data).Width("25%").Format("{0:dd-MM-yyyy}")
column.Command(Function(item) item.Destroy()).Width("10%")
End Sub) _
.DataSource(Sub(ds)
ds.Ajax().ServerOperation(False).Read(Sub(s)
s.Action("GetListaGrid", "listaPedidos")
End Sub).Create(Sub(s)
s.Action("detalhePedido", "Pedidos")
End Sub).Model(Sub(m)
m.Id(Function(p) p.idPedidoDocumentacao)
End Sub).Destroy(Sub(d)
d.Action("apagaPedido", "listaPedidos")
End Sub)
End Sub) _
.Selectable()
)
I can detect the double click with this function, but how do I get the id?
I've done this example with client side api and an equivalent with the MVC extensions.
Create a grid div, to create a grid at run time.
<div id="grid" style="width: 400px;"></div>
Created a row template so that I could give the element an id tag.
<script id="rowTemplate" type="text/x-kendo-tmpl">
<tr>
<td id="EmployeeId">
${ EmployeeID }
</td>
<td>
${ FirstName }
</td>
<td>
${ LastName }
</td>
</tr>
</script>
Initialize the grid and bind data.
<script>
$(document).ready(function () {
$("#grid").kendoGrid({
columns: [
"EmployeeID"
,{
field: "FirstName",
title: "First Name"
},{
field: "LastName",
title: "Last Name"
}
],
dataSource: {
data: [
{
EmployeeID: 0,
FirstName: "Joe",
LastName: "Smith"
}, {
EmployeeID: 1,
FirstName: "Jane",
LastName: "Smith"
}
],
schema: {
model: {
id: "EmployeeID",
fields: {
EmployeeID: {type: "number" },
FirstName: { type: "string" },
LastName: { type: "string" }
}
}
},
pageSize: 10
},
scrollable: {
virtual: true
},
sortable: true,
pageable: true,
rowTemplate: kendo.template($("#rowTemplate").html())
});
//Add a double click event that will return the text in the EmployeeId column.
$('#grid table tr').dblclick(function () {
alert($(this).find("td[id=EmployeeId]")[0].innerText);
});
});
</script>
--EDIT--
I've also gone ahead and created an MVC extensions example, the approach is the same via the template route.
Model class:
public class Employee
{
public int EmployeeId { get; set; }
public string Name { get; set; }
}
View code:
<script type="text/javascript">
function OnDataBound() {
$('#OtherGrid table tr').dblclick(function () {
alert($(this).find("span[id=EmployeeId]")[0].innerText);
});
}
</script>
#(Html.Kendo().Grid<Employee>()
.Name("OtherGrid")
.Columns(columns =>
{
columns.Bound(p => p.EmployeeId).ClientTemplate("<span id=\"EmployeeId\">#: EmployeeId #</span>");
columns.Bound(p => p.Name);
})
.DataSource(dataSource => dataSource
.Ajax() // Specify that the data source is of ajax type
.Read(read => read.Action("GetEmployees", "Home")) // Specify the action method and controller name
)
.Events(e => e.DataBound("OnDataBound"))
)
Controller:
public ActionResult GetEmployees([DataSourceRequest]DataSourceRequest request)
{
List<Employee> list = new List<Employee>();
Employee employee = new Employee() { EmployeeId = 1, Name = "John Smith" };
list.Add(employee);
employee = new Employee() { EmployeeId = 2, Name = "Ted Teller" };
list.Add(employee);
return Json(list.ToDataSourceResult(request));
}
Hope this helps!
I achieved what i was looking for with this js
$('#GridPedidos table tr').live('dblclick', function () {
var grid = $("#GridPedidos").data("kendoGrid");
var dItem = grid.dataItem($(this));
if (dItem != null) {
detalhePedido(dItem.id);
}
});
To open edit mode with double click you need to register the double click event with the grid like this:
var grid = $("#grid").data("kendoGrid");
grid.element.delegate("tbody>tr", "dblclick", function () {
grid.editRow($(this));
});