I'm trying to push object to tags array after axios post but I get this error when push object.
[Vue warn]: Error in render: "TypeError: Cannot read property 'text' of undefined"
What's this happening?
Sorry for my question being bad. I'm just a beginner yet.
It was working at first but I did something and it doesn't work.
I'm not calling 'text' for anything.
javascript/packs/index_vue.js
new Vue ({
el: '#tags',
methods: {
~~~~~~~~~~omit~~~~~~~~~~~
addTag: function(){
this.submitting = true;
const newTag = {
tag: {
title: this.newTagTitle,
status: 0,
tasks: []
}
}
axios.post('/api/tags', newTag)
.then((res) => {
console.log('just created a tag')
this.tags.push(newTag); //error occurring here
this.newTagTitle = '';
this.submitting = false;
this.showNewTagForm = false;
}).catch(error => {
console.log(error);
});
},
addTask: function(tagId, i) { // added by edit
const newTask = {
task: {
text: this.newTaskTextItems[i].text,
deadline: this.newTaskDeadlineItems[i].deadline,
priority: this.newTaskPriorityItems[i].selected
},
tag_task_connection: {
tag_id: tagId
}
}
axios.post('/api/task/create', newTask)
.then(() => {
console.log('just created a task')
newTask.task.limit = Math.ceil((parseDate(this.newTaskDeadlineItems[i].deadline).getTime() - new Date().getTime())/(1000*60*60*24));
this.tags[i].tasks.push(newTask.task);
this.newTaskTextItems[i].text = '',
this.newTaskDeadlineItems[i].deadline = '',
this.newTaskPriorityItems[i].selected = '',
newTask.tasks = '',
newTask.tag_task_connection = ''
}).catch(error => {
console.log(error);
});
}
~~~~~~~~~~omit~~~~~~~~~~~
},
mounted: function () {
axios.get('/api/tags')
.then( res => {
this.tags = res.data.tags,
this.newTaskTextItems = res.data.newTaskTextItems,
this.newTaskDeadlineItems = res.data.newTaskDeadlineItems,
this.newTaskPriorityItems = res.data.newTaskPriorityItems,
this.checkedItems = res.data.checkedItems
})
},
data: {
tags: [],
options: [
{ name: "低", id: 1 },
{ name: "中", id: 2 },
{ name: "高", id: 3 }
],
showNewTagForm: false,
showStatusFrom: false,
changeStatusTag: 0,
deleteConf: false,
deleteTarget: 0,
helloWorld: false,
firstModal: true,
newTagTitle: '',
loading: false,
submitting: false,
newTaskTextItems: '',
newTaskDeadlineItems: '',
newTaskPriorityItems: ''
}
~~~~~~~~~~omit~~~~~~~~~~~
})
views/tags/index.html.slim
.tags
.tag v-for="(tag, i) in tags" :key="i"
.tag-top
.title
h2 v-text="tag.title"
.status.todo v-if="tag.status==0" v-on:click="openStatusForm(tag.id)" 未着手
.status.going v-else-if="tag.status==1" v-on:click="openStatusForm(tag.id)" 進行中
.status.done v-else-if="tag.status==2" v-on:click="openStatusForm(tag.id)" 完了
.delete-button v-if="tag.status==0 || tag.status==1 || tag.status==2"
button.delete.del-modal v-on:click="openDeleteConf(tag.id)" 削除
.tag-content
form.task-form
.task-form-top
input.text type="text" required="" name="text" placeholder="タスクを入力。" v-model="newTaskTextItems[i].text"
.task-form-bottom
.deadline-form
p 締め切り
input.deadline type="date" name="deadline" required="" v-model="newTaskDeadlineItems[i].deadline"
.priority-form
p 優先度
v-select :options="options" v-model="newTaskPriorityItems[i].selected" label="name" :reduce="options => options.id" name="priority" placeholder="選択してください"
.task-form-button
button type="button" v-on:click="addTask(tag.id, i)" タスクを作成
form.tasks
.task v-for="(task, j) in tag.tasks" :key="j"
.task-content :class="{ done: tag.tasks[j].checked }"
.task-top
.check
input.checkbox_check type="checkbox" :value='task.id' v-model="tag.tasks[j].checked" :id="'task' + j"
.task-title :class="{ checked: tag.tasks[j].checked }" v-text="task.text"
.task-priority
.task-priority-title 優先度:
.task-priority-mark.low v-if="task.priority==1" 低
.task-priority-mark.middle v-else-if="task.priority==2" 中
.task-priority-mark.high v-else-if="task.priority==3" 高
.task-bottom
.deadline.tip v-if="task.limit<0" 締め切りを過ぎています
.deadline.tip v-else-if="task.limit==0" 本日締め切り
.deadline(v-else) あと{{ task.limit }}日
.task-clear
button type="button" v-on:click="clearTasks(tag.id)" タスクをクリア
※added by edit from here
routes.rb
Rails.application.routes.draw do
~~~~~~~~~~omit~~~~~~~~~~~
namespace :api, format: 'json' do
resources :tags, only: [:index, :destroy]
post 'tags' => 'tags#create'
post 'task/create' => 'tags#create_task'
post 'task/clear' => 'tags#clear_tasks'
end
end
controllers/api/tag_controller.rb
class Api::TagsController < ApplicationController
protect_from_forgery
skip_before_action :verify_authenticity_token
def index
#tag = Tag.new
#tags = #tag.process
#tasks = Task.all
end
def create
#tag = Tag.new(tag_params)
begin
#tag.save!
rescue ActiveRecord::RecordInvalid => exception
puts exception
end
end
~~~~~~~~~~omit~~~~~~~~~~~
private
def tag_params
# <ActionController::Parameters {"title"=>"param確認テスト", "status"=>0} permitted: true>
params.require(:tag).permit("title", "status")
end
~~~~~~~~~~omit~~~~~~~~~~~
end
models/tag.rb
class Tag < ApplicationRecord
has_many :tag_task_connections, dependent: :destroy
validates :title, :status, presence: true
def process
tags = Tag.all
tasks = Task.all
tags_hash = []
tags.each do |tag|
tag_hash = tag.attributes
tag_hash["tasks"] = []
tag.tag_task_connections.each do |connection|
task = tasks.find(connection.task_id).attributes
task["limit"] = (task["deadline"] - Date.today).to_i
task["checked"] = false
tag_hash["tasks"] << task
end
tags_hash << tag_hash
end
return tags_hash
end
end
views/api/tags/index.json.jbuilder
json.tags #tags
json.newTaskTextItems do
json.array! #tags do |tag|
json.text ''
end
end
json.newTaskDeadlineItems do
json.array! #tags do |tag|
json.deadline ''
end
end
json.newTaskPriorityItems do
json.array! #tags do |tag|
json.selected false
end
end
json.checkedItems do
json.array! #tasks do |task|
json.checked false
end
end
※added by edit from here.
I changed this code but same error occurs.
in index_vue.js
this.tags.push(newTag);
→this.tags.push('something');
※added by edit from here.
when this, no error. push() is wrong?
this.tags.push('something');
→console.log(this.tags)
// this.tags.push('something');
※added by edit from here.
addTag: function(){
~~~~~~~~~~omit~~~~~~~~~~~
axios.post('/api/tags', newTag)
.then((res) => {
console.log(res) // here
console.log('just created a tag')
this.submitting = false;
this.showNewTagForm = false;
console.log(this.tags)
// this.tags.push('something');
this.newTagTitle = '';
newTag.tag = {};
~~~~~~~~~~omit~~~~~~~~~~~
→result of console.log response of axios post
enter image description here
/api/tags
※axios.get is getting this
{"tags":
[{"id":1,
"title":"雑務",
"status":9,
"created_at":"2020-09-05T02:46:06.031Z",
"updated_at":"2020-09-05T02:46:06.031Z",
"tasks":
[{"id":5,
"text":"家賃振り込み",
"deadline":"2020-09-03",
"priority":2,
"created_at":"2020-09-05T02:46:06.082Z",
"updated_at":"2020-09-05T02:46:06.082Z",
"limit":-8,
"checked":false},
{"id":38,
"text":"タスク作成テスト",
"deadline":"2020-09-10",
"priority":2,
"created_at":"2020-09-10T11:03:46.235Z",
"updated_at":"2020-09-10T11:03:46.235Z",
"limit":-1,
"checked":false}]},
{"id":23,
"title":"タグ削除テスト",
"status":0,
"created_at":"2020-09-10T09:13:03.977Z",
"updated_at":"2020-09-10T09:13:03.977Z",
"tasks":[]},
{"id":24,
"title":"タグ削除テスト2",
"status":0,
"created_at":"2020-09-10T09:15:01.551Z",
"updated_at":"2020-09-10T09:15:01.551Z",
"tasks":[]},
{"id":38,
"title":"create_tag_test",
"status":0,
"created_at":"2020-09-10T12:08:12.051Z",
"updated_at":"2020-09-10T12:08:12.051Z",
"tasks":[]},{"id":39,"title":"create_tag_test2","status":0,"created_at":"2020-09-10T12:08:44.929Z","updated_at":"2020-09-10T12:08:44.929Z","tasks":[]},
{"id":40,"title":"create_tag_test3","status":0,"created_at":"2020-09-10T12:10:42.491Z","updated_at":"2020-09-10T12:10:42.491Z","tasks":[]}],
"newTaskTextItems":[{"text":""},{"text":""},{"text":""},{"text":""},{"text":""},{"text":""},{"text":""},{"text":""},{"text":""},{"text":""},{"text":""},{"text":""},{"text":""},{"text":""},{"text":""},{"text":""},{"text":""},{"text":""},{"text":""}],
"newTaskDeadlineItems":[{"deadline":""},{"deadline":""},{"deadline":""},{"deadline":""},{"deadline":""},{"deadline":""},{"deadline":""},{"deadline":""},{"deadline":""},{"deadline":""},{"deadline":""},{"deadline":""},{"deadline":""},{"deadline":""},{"deadline":""},{"deadline":""},{"deadline":""},{"deadline":""},{"deadline":""}],
"newTaskPriorityItems":[{"selected":false},{"selected":false},{"selected":false},{"selected":false},{"selected":false},{"selected":false},{"selected":false},{"selected":false},{"selected":false},{"selected":false},{"selected":false},{"selected":false},{"selected":false},{"selected":false},{"selected":false},{"selected":false},{"selected":false},{"selected":false},{"selected":false}],
"checkedItems":[{"checked":false},{"checked":false}]}
server says
10:43:20 web.1 | Started POST "/api/tags" for ::1 at 2020-09-11 10:43:20 +0900
10:43:20 web.1 | Processing by Api::TagsController#create as JSON
10:43:20 web.1 | Parameters: {"tag"=>{"title"=>"create_tag_test7", "status"=>0, "tasks"=>[]}}
10:43:20 web.1 | Unpermitted parameter: :tasks
10:43:20 web.1 | (0.1ms) begin transaction
10:43:20 web.1 | ↳ app/controllers/api/tags_controller.rb:14:in `create'
10:43:20 web.1 | Tag Create (0.9ms) INSERT INTO "tags" ("title", "status", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["title", "create_tag_test7"], ["status", 0], ["created_at", "2020-09-11 01:43:20.356248"], ["updated_at", "2020-09-11 01:43:20.356248"]]
10:43:20 web.1 | ↳ app/controllers/api/tags_controller.rb:14:in `create'
10:43:20 web.1 | (7.3ms) commit transaction
10:43:20 web.1 | ↳ app/controllers/api/tags_controller.rb:14:in `create'
10:43:20 web.1 | No template found for Api::TagsController#create, rendering head :no_content
10:43:20 web.1 | Completed 204 No Content in 13ms (ActiveRecord: 8.2ms | Allocations: 2708)
Everywhere you declare text please try that:
text: this.newTaskTextItems[i] ? this.newTaskTextItems[i].text : ' ';
then you won't get any errors. And try to console.log(this.newTaskTextItems, this.newTaskTextItems[i], i), maybe some of them are undefined, but some are ok
This is what I fixed.
addTag: function(){
this.submitting = true;
const newTag = {
tag: {
title: this.newTagTitle,
status: 1,
tasks: [],
errors: {
text: '',
deadline: '',
priority: ''
}
}
}
axios.post('/api/tags', newTag)
.then(() => {
console.log('just created a tag')
this.submitting = false;
this.showNewTagForm = false;
this.newTagTitle = '';
if (this.errors != '') {
this.errors = ''
}
var newTaskTextItem = {text: ''};
var newTaskDeadlineItem = {deadline: ''};
var newTaskPriorityItem = {selected: 0};
this.newTaskTextItems.push(newTaskTextItem); //I got the error, because I hadn't been doing this.
this.newTaskDeadlineItems.push(newTaskDeadlineItem);
this.newTaskPriorityItems.push(newTaskPriorityItem);
this.tags.push(newTag.tag);
}).catch(error => {
if (error.response.data && error.response.data.errors) {
this.errors = error.response.data.errors;
}
this.submitting = false;
this.showNewTagForm = false;
});
},
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'm trying to learn Backbone and can't seem to match data from the fetch function into my Underscore template. How can can I get the children array in my JSON and match it to the template?
The Backbone.View looks like this:
var Projects = Backbone.Collection.extend({
url: '/tree/projects'
});
var Portfolio = Backbone.View.extend({
el: '.page',
render: function () {
var that = this;
var projects = new Projects();
projects.fetch({
success: function (projects) {
var template = _.template($('#projects-template').html());
that.$el.html(template({projects: projects.models}));
}
})
}
});
At the url: http://localhost:3000/portfolio/api/tree/projects
The JSON returned looks like this:
{
id:"projects",
url:"http://localhost:8888/portfolio/projects",
uid:"projects",
title:"Projects",
text:"",
files:[
],
children:[
{
id:"projects/example-1",
url:"http://localhost:8888/portfolio/projects/example-1",
uid:"example-1",
title:"Example 1",
images:"",
year:"2017",
tags:"Website",
files:[
],
children:[
]
},
{
id:"projects/example-2",
url:"http://localhost:8888/portfolio/projects/example-2",
uid:"example-2",
title:"Example #"2
text:"Example 2's text",
year:"2016",
tags:"Website",
files:[
{
url:"http://localhost:8888/portfolio/content/1-projects/4-example-2/example_ss.png",
name:"example_ss",
extension:"png",
size:244845,
niceSize:"239.11 kB",
mime:"image/png",
type:"image"
}
],
children:[
]
},
]
}
My Underscore file looks like this:
<script type="text/template" id="projects-template">
<h4>tester</h4>
<div>
<% _.each(projects.children, function (project) { %>
<div>
<div><%= project.get('year') %></div>
<div><%= project.get('title') %></div>
<div><%= project.get('tags') %></div>
</div>
<% }); %>
</div>
</script>
You can define a parse method on the collection:
var Projects = Backbone.Collection.extend({
url: '/tree/projects',
parse: function(response){
/* save other data from response directly to collection if needed.
for eg this.title = response.title; */
return response.children; // now models will be populated from children array
}
});
Do not use parse
While I usually agree with TJ, using parse on the collection is more like a hack than a definite solution. It would work only to get the children projects of a project and nothing more.
The parse function shouldn't have side-effects on the collection and with this approach, changing and saving fields on the parent project wouldn't be easily possible.
It also doesn't deal with the fact that it's a nested structure, it's not just a wrapped array.
This function works best when receiving wrapped data:
{
data: [{ /*...*/ }, { /*...*/ }]
}
Models and collections
What you have here are projects that have nested projects. A project should be a model. You also have files, so you should have a File model.
Take each resource and make a model and collection classes with it. But first, get the shared data out of the way.
var API_ROOT = 'http://localhost:8888/';
File
var FileModel = Backbone.Model.extend({
defaults: {
name: "",
extension: "png",
size: 0,
niceSize: "0 kB",
mime: "image/png",
type: "image"
}
});
var FileCollection = Backbone.Collection.extend({
model: FileModel
});
Project
var ProjectModel = Backbone.Model.extend({
defaults: function() {
return {
title: "",
text: "",
files: [],
children: []
};
},
getProjects: function() {
return this.get('children');
},
setProjects: function(projectArray, options) {
return this.set('children', projectArray, options);
},
getFiles: function() {
return this.get('files');
},
getSubProjectUrl: function() {
return this.get('url');
}
});
var ProjectCollection = Backbone.Collection.extend({
model: ProjectModel,
url: API_ROOT + '/tree/projects'
});
Project view
Then, make a view for a project. This is a simple example, see the additional information for tips on optimizing the rendering.
var ProjectView = Backbone.View.extend({
template: _.template($('#projects-template').html()),
initialize: function(options) {
this.options = _.extend({
depth: 0, // default option
}, options);
// Make a new collection instance with the array when necessary
this.collection = new ProjectCollection(this.model.getProjects(), {
url: this.model.getSubProjectUrl()
});
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
this.$projectList = this.$('.list');
// use the depth option to avoid rendering too much projects
if (this.depth > 0) this.collection.each(this.renderProject, this);
return this;
}
renderProject: function(model) {
this.$projectList.append(new ProjectView({
model: model,
depth: depth - 1
}).render().el);
}
});
With a template like this:
<script type="text/template" id="projects-template">
<h4><%= title %></h4>
<span><%= year %></span><span><%= tags %></span>
<p><%= text %></p>
<div class="list"></div>
</script>
Using the view:
var model = new ProjectModel({ id: "project" });
model.fetch({
success: function() {
var project = new ProjectView({
model: model,
depth: 2
});
}
});
Additional info
Nested models and collections
Efficiently rendering a list
I have a component called Items that lives within a parents called ItemsContainer. When a button in Items is clicked an Ajax function is called to delete that Item.
At the moment however I am receiving a 500 error message and am not sure why.
Item Component
class Item extends React.Component{
constructor(props) {
super()
this.state = {
name: '',
price: 0,
code: '',
id: ''
}
}
componentWillMount() {
this.setState({
name: this.props.data.name,
price: this.props.data.price,
code: this.props.data.code,
id: this.props.data.id
})
}
deleteItem() {
let finalUrl = '/items/' + this.state.id;
$.ajax({
type: "DELETE",
url: finalUrl, /* THIS URL IS CALLING CORRECTLY ie. /items/8 */
dataType: "json",
success: function(response) {
console.log("successfully deleted");
},
error: function () {
console.log("error");
}
})
}
render(){
let itemName = this.props.data.name
let itemCode = this.props.data.code
let itemQuantity = 1
let itemPrice = (this.props.data.price * itemQuantity).toFixed(2)
const itemId = this.props.data.id
return(
<tr>
<td>{itemName}</td>
<td>{itemCode}</td>
<td>{itemQuantity}</td>
<td><button className="btn btn-warning" onClick={this.deleteItem.bind(this)}>Remove</button></td>
<td>£{itemPrice}</td>
</tr>
)
}
}
Rails Items Controller
class ItemsController < ApplicationController
def create
#item = Item.new(item_params)
if #item.save
render partial: 'items/item', locals: {item: #item}
else
render json: #item.errors.to_json
end
end
def destroy
if #item.destroy
render partial: 'items/item', locals: {item: #item}
else
render json: #item.errors.to_json
end
end
private
def item_params
params.require(:item).permit(
:name,
:price,
:code,
:id
)
end
end
Creating a new Item is working as expected but I can't work out why I am receiving my 500 error for my delete action. Any help would be much appreciated.
Please check your destroy method in rail controller.
There is no definition for #item hence 500 internal server error :)