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.
Related
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"
}
}
I have the following checkboxes set up:
<label v-for="service in team.services">
<input type="checkbox" v-model="form.services" :id="service.name" :value="service.id"/>
</label>
These are displayed correctly but when checking one checkbox they all get checked as the form.services model gets set to false / true.
However, when changing the model to another data attribute e.g. 'services' everything works as expected. Any reason why this isn't working within SparkForm?
Example data:
data: function() {
return {
form: new SparkForm({
userId: null,
services: [] // always only gets set as true / false
}),
services: [], // works fine
}
},
new Vue({
el: '#app',
data(){
return {
form : {
services : []
},
team : {
services : [
{
"name" : "Service name #1",
"id" : 1
},
{
"name" : "Service name #2",
"id" : 2
}
]
}
}
}
});
<div id="app">
<label v-for="service in team.services">
<input type="checkbox" v-model="form.services" :value="service.id"/>{{service.name}}
</label>
{{form.services}}
</div>
I have to add/post data form. But the form dynamically can increase as user 'click' on a button. I've already browse about it and there some answer i get like using $request->all() to fetch all data from input forms.
And then my problem is, my app using VueJS as front-end. Is there any some configuration on VueJS script to post all data from that dynamic form??
My Blade template that will be increase dynamically:
<div id="form-message">
{!! Form::text('rows[0][DestinationNumber]', null, [
'id' => 'recipient',
'class' => 'form-control',
'v-model' => 'newMessage.DestinationNumber'
])
!!}
{!! Form::textarea('rows[0][TextDecoded]', null, [
'rows' => '3',
'id' => 'recipient',
'class' => 'form-control',
'v-model' => 'newMessage.TextDecoded'
])
!!}
</div>
That zero number will increase depends on how much user click add button.
And then here my VueJS script
var newSingleMessage = new Vue({
el: '#newsinglemsg',
data: {
newMessage: {
DestinationNumber: '',
TextDecoded: ''
},
},
methods: {
onSubmitForm: function(e) {
e.preventDefault();
var message = this.newMessage;
this.$http.post('api/outbox', message);
message = { DestinationNumber: '', TextDecoded: '' };
this.submitted = true;
}
}
});
On laravel controller, i have simple logic to test result how data passed.
$input = $request->all();
$output = dd($input);
return $output;
And, I test it using 2 additional form. So, the data should be 3 rows. The result (checked from FireBug) to be like this
{"DestinationNumber":"1234567890","TextDecoded":"qwertyuio"}
Data passed just one, and then the type is JSON. Even I use return $output->toArray(), type still JSON.
Oh yeah, once more. Idk how to make the zero number increase dynamically using javascript. When testing, i just manual add the form. Here my add click function javascript
var i = 0,
clone = $('#form-message').clone(),
recipient = document.getElementById('recipient');
recipient.setAttribute('name', 'rows['+ i +'][DestinationNumber]');
clone.appendTo('.form-message:last');
i++;
For second and next rows, name attribute not added on the input elements.
Thanks
You're mixing blade and jquery and vue in a way that is pretty confusing. Check out this JS fiddle that accomplishes all of this with Vue:
https://jsfiddle.net/cr8vfgrz/10/
You basically have an array of messages that are automatically mapped to inputs using v-for. As those inputs change, your messages array changes. Then when submit is pressed, you just post this.messages and the array of messages is sent to server. Then you can clear the array to reset the form.
Template code:
<div id="form-message">
<button class="btn btn-default" #click="addNewMessage">New Message</button>
<template v-for="message in messages">
<input type="text" v-model="message.DestinationNumber" class="form-control">
<textarea rows="3" v-model="message.TextDecoded" class="form-control"></textarea>
</template>
<button class="btn btn-success" #click.prevent="submitForm">Submit</button>
</div>
Vue code:
var newSingleMessage = new Vue({
el: '#form-message',
data: {
messages: [
{
DestinationNumber: '',
TextDecoded: ''
}
],
submitted:false
},
methods: {
addNewMessage: function(){
this.messages.push({
DestinationNumber: '',
TextDecoded: ''
});
},
submitForm: function(e) {
console.log(this.messages);
this.$http.post('api/outbox', {messages:this.messages})
.then(function(response){
//handle success
console.log(response);
}).error(function(response){
//handle error
console.log(response)
});
this.messages = [{ DestinationNumber: '', TextDecoded: '' }];
this.submitted = true;
}
}
});
Edit:
In the controller you can use $request->input('messages'); which will be the array of messages. You can insert multiple new Outbox model using:
Outbox::insert($request->input('messages'));
or
foreach($request->input('messages') as $message){
Outbox::create($message);
}
I'm using typeahead.js to get movie info from themoviedb api. I need when the user type the movie's title get the year of the movie and the ID of the movie to be added automatically to other inputs.
So when the user using the input Movie title and he click on the suggested titles, It will automatically add the year and the movie id to the other inputs
HTML Code
<input class="typeahead" placeholder="Movie Title Here"><br>
<input class="year" placeholder="Year Here">
<input class="id" placeholder="Year ID">
JS code
Look close to the return (at Line 12) there is the info I need to be transferred to other inputs
var movies = new Bloodhound({
datumTokenizer: function (datum) {
return Bloodhound.tokenizers.whitespace(datum.value);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
limit: 10,
remote: {
url: 'http://api.themoviedb.org/3/search/movie?api_key=470fd2ec8853e25d2f8d86f685d2270e&query=%QUERY&search_type=ngram',
filter: function (movies) {
// Map the remote source JSON array to a JavaScript array
return $.map(movies.results, function (movie) {
return {
id: movie.id,
value: movie.original_title,
year: (movie.release_date.substr(0,4) ? movie.release_date.substr(0,4) : '')
};
});
}
}
});
// Initialize the Bloodhound suggestion engine
movies.initialize();
// Instantiate the Typeahead UI
$('.typeahead').typeahead({
hint: true,
highlight: true
}, {
displayKey: 'value',
source: movies.ttAdapter(),
templates: {
empty: [
'<div class="empty-message">',
'unable to find any Best Picture winners that match the current query',
'</div>'
].join('\n'),
suggestion: Handlebars.compile('<p><strong>{{value}}</strong> – {{year}}</p>')
}
});
here is my code in action on jsfiddle to try yourself:
http://jsfiddle.net/Jim_Toth/ss8L24x8/
I've added a way to auto-populate the associated input controls here:
http://jsfiddle.net/Fresh/cmq80qx3/
The key part of the code to achieve the auto-population is:
bind("typeahead:selected", function (obj, datum, name) {
$('.year').val(datum.year);
$('.id').val(datum.id);
});
This code specifies the function which should be called when a typeahead value is selected, in this case it appropriately sets the values of the year and id inputs.
I am trying to submit an aria template form http://ariatemplates.com/,the submission is done to a Spring MVC controller/servlet.
The form is getting submitted all right but i am not able to get the values of the aria elements like date picker,text box etc in the controller.
Request.getParameter is of no use.
Any Help will be appreciated.
Here is my sample tpl file,js file and the Spring Controller.
TPL File
{Template {
$classpath:'view.Turnover',
$hasScript : true
}}
{macro main()}
<form action="test.do" method="POST" id="turnoverform">
<div style="float:left;padding-top: 3em;padding-bottom: 3em;padding-right: 3em;">
{#aria:Div {
sclass : "basic",
width : 740,
height : 300
}}
<p style="font-family:Arial,Helvetica,sans-serif;font-size: medium;">Create Turnover Report</p>
<hr />
{#aria:DatePicker {
label: " begin date:",
labelWidth:190,
width:330,
helptext:"Type date or select",
}/}
{#aria:DatePicker {
margins:"x x x 20",
label: "end date:",
labelWidth:190,
helptext:"Type date or select",
width:330,
}/}
<br/>
<br/>
<br/>
{#aria:TextField {
label : "User id",
labelPos : "left",
helptext : "ID",
width : 250,
block : true,
labelWidth : 80,
bind : {
"value" : {
inside : data,
to : 'value' }
}
}/}
<br />
{/#aria:Div}
<br />
{#aria:IconButton {
icon: "std:confirm",
label:"Create",
width : 300,
tooltip : "Click on this to create a Report",
block: true,
onclick : {
fn : buttonClick
}
} /}
</div>
</form>
{/macro}
{/Template}
Javascript File :
Aria.tplScriptDefinition({
$classpath : "view.TurnoverScript",
$prototype : {
/**
* Callback for the click event on the first button.
* #param {aria.DomEvent} evt Click event
*/
buttonClick : function (evt) {
aria.core.IO.asyncFormSubmit({
formId : "turnoverform",
callback : {
fn : this.onSuccess,
onerror : this.onError,
scope : this
}
});
},
onSuccess : function (evt, args) {
alert("The Template has been created");
//this.$json.setValue(["view:Dialog"], "dialogOpen", true);
},
onError : function (evt, args) {
alert("The Template has not been created due to some Error");
}
}
});
in Aria Templates you don't work normally with DOM elements but with the data model.
The way to achieve what you want is to bind those values to the datamodel using the bind property
{#aria:DatePicker {
label: " begin date:",
labelWidth:190,
width:330,
helptext:"Type date or select",
bind : {
value : {
inside : data,
to : "begin_date"
}
}
}/}
Your datamodel would now contain those values, try to modify those values and see the content of this.data in your template script.
To submit the data you have two options,
Template Script through aria.core.Io.asyncRequest (or maybe the RequestMgr, depending on your application complexity).
This method takes a data string that in case of POST requests is the message body. It has to be a string so you can use aria.utils.json.JsonSerializer.serialize() to convert your datamodel into a string.
aria.utils.json.JsonSerializer.serialize(this.data, config)
In the previous snippet of code config is optional, if provided it should match this bean.
Module controller through submitJsonRequest
The good thing about using a controller is that you separate the logic of connecting to a server from the template and that you can send directly an object as data, serialization is done internally.
The drawback is that you'll probably have to configure your UrlService to convert actions to actual URL. Few more info here