How to create a simple modal for create & edit functions in vue.js? - laravel

I'm trying to create a model to be able to edit existing data? How would I go about doing that?
I'm using a <el-form> to create a new entry in this case I'm creating questions, I want to reuse this for the edit and have the data from the entry added into the form.
This is my form right now
<el-dialog title="Nuevo" :loading="loading" :visible="dialogFormVisible" :visible.sync="dialogFormVisible">
<el-form label-position="top" ref="form" :model="form" :rules="rules">
<el-row>
<el-form-item label="Pregunta" prop="question">
<el-input v-model="form.question"></el-input>
</el-form-item>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="Seccion" prop="survey_section_id">
<el-select v-model="form.survey_section_id" placeholder="Selecionar">
<el-option v-for="section in survey_section" :key="section.id" :label="section.title"
:value="section.id">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="Tipo de Respuesta" prop="response_type">
<el-select v-model="form.response_type_id" placeholder="Selecionar">
<el-option v-for="type in response_type" :key="type.id" :label="type.type" :value="type.id">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item label="Opcional" prop="optional">
<el-switch v-model="form.optional"></el-switch>
</el-form-item>
</el-col>
</el-row>
</el-form>
<span slot="footer">
<el-button type="info" #click="dialogFormVisible = false">Cancelar
</el-button>
<el-button type="primary" :loading="loading" #click="submit('form')">Guardar
</el-button>
</span>
</el-dialog>
What do I need to do to turn this into a modal and use it for my editing as well?
Ask me anything, I can provide any code that I have that is needed.
Thanks

This is a common problem. Of course, a lot of these things come down to personal preference or opinion, but here are a few things to consider:
Usually when solving this problem, you display all of your existing models in some way (ie. a list), with an "edit" button. When I am doing this, I use an object that maps model IDs to their corresponding model objects as the underlying data model for this list. This makes it easy to replace a model with an updated version (models[model.id] = model).
Binding the model to the form directly (eg. :model="the model you want to edit") is usually a bad implementation. This is because any changes that are made during editing would be immediately written to the model. The problem with that is, if your save() function fails, then you have to change everything back. A better implementation is to clone the model, and bind that object to the form.
You can use a computed property for the modal's title/header. I usually have a data property called mode, which will be either "Create" or "Edit", and then I have a computed property that returns mode + ' Model', where 'Model' is the name of the model – in your case, "Pregunta".
Since the create and update functions are usually use different API endpoints (and HTTP methods), your "Save / Update" button needs to call the right one. Again, I use the mode property to handle this (eg. <button #click="mode == 'edit' ? update : save">)
The following code should give you a good starting point for having a modal you can use for both creating & editing models, as well as the basic structure for most CRUD components.
<template>
<div>
<template v-for="model in modelsArray">
... <!-- Display your existing models however you want -->
Edit
</template>
<button #click="create">Create New Model</button>
<el-dialog
:title="modalTitle"
:loading="loading"
:visible.sync="dialogFormVisible"
>
<el-form :model="formModel">
...
</el-form>
<span slot="footer">
<el-button type="info" #click="cancel">Cancelar</el-button>
<el-button
type="primary"
:loading="loading"
#click="mode == 'edit' ? update : save"
>
{{ mode }}
</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
data() {
mode: '',
dialogFormVisible: false,
loading: false,
formModel: {},
models: { // an object mapping your existing model IDs
1: { id: 1, ...}, // to their corresponding model objects
2: { id: 2, ...},
3: { id: 3, ...},
...
}
},
...,
computed: {
modelsArray() {
return Object.values(this.models);
},
modalTitle() {
return `${ this.mode } Model`;
}
},
methods: {
create() {
this.mode = 'Create';
this.dialogFormVisible = true;
},
edit(model) {
this.mode = 'Edit';
this.formModel = _.cloneDeep(model); // See note below
this.dialogFormVisible = true;
},
save() {
// POST the new model (this.formModel) to your server using your ajax library of choice
// after the ajax call returns, do the following:
this.models.push(the newly created model); // usually returned from the server as part of this ajax call
this.reset();
},
update() {
// PUT the new model (this.formModel) to your server using your ajax library of choice
// After the ajax call returns, replace the updated model in the `models` object, and reset the form:
this.models[this.formModel.id] = _.cloneDeep(this.formModel);
this.reset();
},
reset() {
this.dialogFormVisible = false;
this.loading = false;
this.formModel = {
... // put default form values here
};
}
},
mounted() {
this.reset();
}
}
</script
NOTE: _.cloneDeep is from lodash. If you're not using lodash, you can use one of these methods to clone the model.

Related

Output user first name

I want to get the name of the user to put it on an h1.
What dies this line stand for?
#select="option => selected = option">
I'm using Buefy for the vue components.
<template>
<section>
<div class="field">
<b-switch v-model="keepFirst">
Keep-first <small>(will always have first option pre-selected)</small>
</b-switch>
</div>
<p class="content"><b>Selected:</b> {{ selected }}</p>
<b-field label="Find a name">
<b-autocomplete
v-model="name"
placeholder="e.g. Anne"
:keep-first="keepFirst"
:data="filteredDataObj"
field="user.first_name"
#select="option => selected = option">
</b-autocomplete>
</b-field>
</section>
</template>
<script>
import data from '#/assets/data_test.json'
// Data example
// [{"id":1,"user":{"first_name":"Jesse","last_name":"Simmons"},"date":"2016-10-15 13:43:27","gender":"Male"},
// {"id":2,"user":{"first_name":"John","last_name":"Jacobs"},"date":"2016-12-15 06:00:53","gender":"Male"},
// {"id":3,"user":{"first_name":"Tina","last_name":"Gilbert"},"date":"2016-04-26 06:26:28","gender":"Female"},
// {"id":4,"user":{"first_name":"Clarence","last_name":"Flores"},"date":"2016-04-10 10:28:46","gender":"Male"},
// {"id":5,"user":{"first_name":"Anne","last_name":"Lee"},"date":"2016-12-06 14:38:38","gender":"Female"}]
export default {
data() {
return {
data,
keepFirst: false,
name: '',
selected: null
}
},
computed: {
filteredDataObj() {
return this.data.filter((option) => {
return option.user.first_name
.toString()
.toLowerCase()
.indexOf(this.name.toLowerCase()) >= 0
})
}
}
}
</script>
# is shorthand for v-on:, so it's handling a select event with a function that receives option as a parameter and assigns it to selected.
Since v-model is bound to name, you should be able to do <h1>{{name}}</h1> to have the same value show up in an H1.
The data section has the main variables for your object. name is there. There is also a computed (named filteredDataObj) that should return an array (length of zero or one) with the matching test data. If you want other fields (like id) you would need to look there. Something like
{{filteredDataObj.length ? filteredDataObj.id : ''}}
would give the id if name matched anything in the data set.

Angular 5 - Reactive forms doesn't validate form on submit

I have a simple form as below:
some.component.html
<form class="example-form" novalidate (ngSubmit)="onSubmit()" autocomplete="off" [formGroup]="testform">
<input type="text" formControlName="name" class="form-control" placeholder="Enter name" required/>
<app-show-errors [control]="claimform.controls.name"></app-show-errors>
<button type="submit" (click)="onSubmit()">Next</button>
</form>
some.component.ts
ngOnInit() {
this.testform= new FormGroup({
name: new FormControl('', { validators: Validators.required})
}, {updateOn: 'submit'});
}
onSubmit() {
if (this.testform.valid) {
alert('saving data');
} else {
this._validationService.validateAllFormFields(this.testform);
}
}
validationService.ts
validateAllFormFields(formGroup: FormGroup) {
Object.keys(formGroup.controls).forEach(field => {
const control = formGroup.get(field);
if (control instanceof FormControl) {
control.markAsTouched({ onlySelf: true });
} else if (control instanceof FormGroup) {
this.validateAllFormFields(control);
}
});
}
Reference
Problem
The form will validate on submit if left blank, but even after filling the value when I check this.testform.valid it returns false. But if I remove updateOn:'submit' on form then it validates on blur of input control and when value is entered it validates form return true. Not sure if updateOn is working fine or not or whether I've implemented this in a proper way. Could someone point me in the right direction.
in your HTML you have two calls to onSubmit() function, from submit button:
<button type="submit" (click)="onSubmit()">Next</button>
and from the form:
<form class="example-form"
ovalidate
(ngSubmit)="onSubmit()"
autocomplete="off"
[formGroup]="testform">
The first call to be triggered is the button's trigger, which actually does nothing in terms of updating your reactive form, since you set FormGroup's option to {updateOn: 'submit'}. The second call to be triggered is the form's trigger, which does actual form update.
Here is FormGroup directive config:
#Directive({
selector: '[formGroup]',
providers: [formDirectiveProvider],
host: {'(submit)': 'onSubmit($event)', '(reset)': 'onReset()'},
exportAs: 'ngForm'
})
as we can see in host property DOM form's submit (triggered by hitting ENTER while focused within form or clicking form's submit button) will call onSubmit() function:
onSubmit($event: Event): boolean {
(this as{submitted: boolean}).submitted = true;
syncPendingControls(this.form, this.directives);
this.ngSubmit.emit($event);
return false;
}
which then will call syncPendingControls() function:
export function syncPendingControls(form: FormGroup, directives: NgControl[]): void {
form._syncPendingControls();
directives.forEach(dir => {
const control = dir.control as FormControl;
if (control.updateOn === 'submit' && control._pendingChange) {
dir.viewToModelUpdate(control._pendingValue);
control._pendingChange = false;
}
});
}
which updates a model at last.
So, in your case, just remove (click)="onSubmit()" from the submit button:
<button type="submit">Next</button>
also you do not need required DOM element property on your input, since you set it using Reactive Forms API validators: Validators.required and since you set your form to novalidate which cancels HTML5 form validation.

Conditional v-if is working only for the first time?

I have this in my view:
<div class="already_voted" v-if="already_voted" >
<p>You already voted or your are not allowed to vote</p>
</div>
This is my method :
upvote: function(com_id) {
var comment_id = {
comment_id :com_id
}
this.$http.post('/blog/article/comment/upvote', comment_id).then(function(response){
upvote_total= response.data.upvote_value;
this.already_voted = response.data.already_voted;
this.$dispatch('child-msg', this.already_voted);
$('.upvote_class_' + com_id ).text(upvote_total);
$('.isDisabledUpvote_' + com_id).addClass('disabled');
$('.isDisabledDownvote_' + com_id).removeClass('disabled');
},function(response){
});
},
Im getting value on click and if its true it need to show this div.
Problem is that this div is showed only for first time when already_voted is true and thats it. Next time when its true nothing happend. Any suggestion?
It looks like you are mixing jQuery and Vue, which should be avoided unless you have a specific reason to do so. Instead you should bind attributes to data. As a basic version of what you are doing you could bind both the disabled attribute and the message to a voted flag:
Markup
<div id="app">
<div v-if="voted">
You have already voted!
</div>
<button v-bind:disabled="voted" #click="vote()">
Vote
</button>
<button v-bind:disabled="!voted" #click="removeVote()">
Un-Vote
</button>
</div>
View Model
new Vue({
el: '#app',
methods: {
vote(){
this.voted = true;
},
removeVote(){
this.voted = false;
}
},
data: {
voted: false
}
});
Here I'm simply binding the disabled attribute using v-bind to the voted flag to disabled the buttons and am using v-if to show a message if the voted flag is true.
Here's the JSFiddle: https://jsfiddle.net/05sbjqLL/
Also be aware that this inside an anonymous function refers to the anonymous function itself, so either assign this to something (var self = this) outside the function or use an arrow function if using ES6.
EDIT
I've updated the JSFiddle to show you how you might handle your situation based on you comments:
https://jsfiddle.net/umkvps5g/
Firstly, I've created a directive that will allow you to initiate your variable from your cookie:
Vue.directive('init', {
bind: function(el, binding, vnode) {
vnode.context[binding.arg] = binding.value;
}
})
This can now be used as:
<div v-init:voted="{{ $request->cookie('voted') }}"></div>
I simply disabled the button to show you how to bind attributes to data, there's loads more that can be done, for example showing the message after a user clicks the button, I've just added a click counter and bound thev-if to that instead, so the message doesn't show until a user clicks the button:
<div v-if="vote_attempts">
You have already voted!
</div>
Then in vote() method:
vote() {
this.voted = true;
this.vote_attempts++;
},
Then data:
data: {
voted: false,
vote_attempts: 0
}

Apply binding once in knockout component

In my web application I have (let's say) 2 tabs, both are custom knockout components. Their visibility is controlled by the following syntax:
<div id="page" data-bind="component: { name: currentTab }"></div>
where currentTab is an observable with the name of the current tab.
In both tabs I have visualisations with D3.js, using custom bindings. The problem is that these custom bindings are reinitialised after opening a tab. Is there a way to only load them once so that they don't need to be redrawn?
Note that the component's viewmodels aren't reinitialised, as they are created with the { instance: new viewModel() } trick:
define(['knockout', 'text!./tab-one.html', 'jquery'], function(ko, template, $) {
function ViewModel() {
var self = this;
};
return {
viewModel: { instance: new ViewModel() },
template: template
};
});
You may need to change slightly your approach:
<div id="page">
<div id="tab-1" data-bind="visible: (currentTab() == 'drawing-1'), component:{name:'drawing-1'}"></div>
<div id="tab-2" data-bind="visible: (currentTab() == 'drawing-2'), component:{name:'drawing-2'}"></div>
</div>
As alternative, you can add some more flexibility to your components if you provide a viewmodel, for example to encapsulate the logic of the visibility inside the component itself, and moreover - if you can also eventually skip the first redraw at component initialization:
This is just an example, but you can see the idea:
ko.components.register('drawing', {
viewModel: function(params) {
// Behaviors
this.active1 = params.currentTab == 'drawing-1';
this.active2 = params.currentTab == 'drawing-2';
this.draw = ko.computed(function() {
if (!ko.computedContext.isInitial()) {
// do some changes to the drawing
}
});
},
template:
'<div data-bind="visible: active1">'+
// drawing 1
'</div>'+
'<div data-bind="visible: active2">'+
// drawing 2
'</div>'
});

Multiple views for one model in AngularJS

I'm learning AngularJS framework and my background is BackboneJS and it seems I can not figure
out conventional way to do next thing:
I have a readonly list of elements each of which has 'edit' button that switches this
particular element to an edit mode. In edit mode I need to render input elements instead
of spans, p's etc.
The way to do this in Backbone.js is simply create EditView and pass model to it, but I don't have any idea how this works in Angular.
I pass data to the scope and render the readonly list and when user clicks on 'edit' button
in the element how should I change view for the element?
Thanks!
Angular makes this task simpler than Backbone by using a model for editing, then you update the original list and clear the edit model.
The view:
<div ng-app='App'>
<div ng-controller='mainCtrl'>
<div ng-controller='childCtrl'>
<strong>Controller</strong>
<ul>
<li ng-repeat='item in list'>{{item.name}} <button ng-click='edit($index)'>+</button></li>
</ul>
<div ng-show='editModel.name'>
Name: <input type="text" ng-model='editModel.name'/> <button ng-click='save()'>Save</button><button ng-click='cancelEdit()'>Cancel</button>
</div>
</div>
</div>
The angular app:
angular.module('App', [])
.controller('mainCtrl', ['$scope', function($scope){
$scope.list = [ {name:'item 1'}, {name:'item 2'}, {name:'item 3'} ];
}])
.controller('childCtrl', ['$scope', function($scope){
$scope.editModel = {};
$scope.edit = function(index){
$scope.editModel.index = index;
$scope.editModel.name = $scope.list[index].name;
};
$scope.cancelEdit = function(){
$scope.editModel = {};
};
$scope.save = function(){
if($scope.editModel.hasOwnProperty('index')){
$scope.list[$scope.editModel.index] = $scope.editModel;
}
else if($scope.editModel.name && $scope.editModel.name.length){
$scope.list.push($scope.editModel);
}
$scope.cancelEdit();
};
}]);
JsFiddle: http://jsfiddle.net/guilhermenagatomo/Bp6bY/
I'm using the controller nesting so the list can be used by other controllers you have in the app, or maybe you can create a controller just to take care of the editing.

Resources