Form modal binding in laravel with vue js - javascript

I have 2 models Tour.php
public function Itinerary()
{
return $this->hasMany('App\Itinerary', 'tour_id');
}
and Itinerary.php
public function tour()
{
return $this->belongsTo('App\Tour', 'tour_id');
}
tours table:
id|title|content
itineraries table:
id|tour_id|day|itinerary
In tour-edit.blade.php view I have used vue js to create or add and remove input field for day and plan dynamically.
Code in tour-create.blade.php
<div class="row input-margin" id="repeat">
<div class="col-md-12">
<div class="row" v-for="row in rows">
<div class="row">
<div class="col-md-2">
<label >Day:</label>
<input type="text" name="day[]"
class="form-control">
</div>
<div class="col-md-8">
{{ Form::label('itinerary', " Tour itinerary:", ['class' => 'form-label-margin'])}}
{{ Form::textarea('itinerary[]',null, ['class' => 'form-control','id' => 'itinerary']) }}
</div>
<div class="col-md-2">
<button class="btn btn-danger" #click.prevent="deleteOption(row)">
<i class="fa fa-trash"></i></button>
</div>
</div>
</div>
<div class="row">
<button class="btn btn-primary add" #click.prevent="addNewOption" >
<i class="fa fa-plus"></i> Add Field</button>
</div>
</div>
</div>
I want to populate these fields with their respective data. But all data i.e itinerary belonging to a tour are being displayed in itinerary textbox in JSON format.
My vue js sript is:
<script>
var App = new Vue({
el: '#repeat',
data: {
day:1 ,
rows:[
#foreach ($tour->itinerary as $element)
{day: '{{$element->day}}', plan: '{{$element->plan}}'},
#endforeach
]
},
methods: {
addNewOption: function() {
var self = this;
self.rows.push({"day": "","itinerary":""});
},
deleteOption: function(row) {
var self = this;
self.rows.splice(row,1);
},
}
});
</script>

I would avoid mixing blade into JavaScript, instead the best option is to make an ajax call to an api route which returns your data in json, which can then be processed by Vue:
methods:{
getItinerary(){
axios.get('api/itinerary').then(response => {
this.itinerary = response.data;
})
}
}
However, with this approach you will likely need to use vue-router rather than laravel web routes, which puts us into SPA territory.
If that's not an option (i.e. you still want to use blade templates), you should take a look at this answer I gave the other day which shows you how to init data from your blade templates.
What you seem to be doing is using laravel's form model binding to populate your forms, not Vue, so your model data is not bound to the view. So, you will need to decide which one you want to use. If it's vue you just want to use a normal form and bind the underlying data to it using v-model:
Now any updates in the view will automatically be updated by Vue. I've put together a JSFiddle that assumes you will want to continue using Laravel web routes and blade templates to show you one approach to this problem: https://jsfiddle.net/w6qhLtnh/

Related

Reuse html template in Angular project

I have this html template file, range-details-dialog.tpl.html
<div class="modal-header clearfix text-left">
<h5>Update Range</h5>
</div>
<div class="modal-body">
<form name="form" role="form" class="ng-pristine ng-valid" novalidate ng-submit="updateRange()">
<div class="form-group-attached">
<div class="row">
<div class="col-sm-12">
<div class="form-group form-group-default input-group p-l-10 p-r-10" ng-class="{ 'has-error' : form.$invalid }">
<p ng-show="form.rangeDaily.$error.min" class="help-block">Daily range more than £5</p>
</div>
</div>
</div>
</div>
</form>
<div class="row">
<div class="col-sm-8"></div>
<div class="col-sm-4 m-t-10 sm-m-t-10">
<button type="button" class="btn btn-primary btn-block m-t-5"
ng-disabled="form.$invalid || promise" promise-btn="promise" ng-click="updateRange()">Update</button>
</div>
</div>
</div>
Then I want to have another file forced-range-details-dialog.tpl.html
These two files could be one file instead with dynamically populated placeholders.
These are the places were substitution would be needed:
<h5>Update Range</h5> would become <h5>Update Forced Range</h5>
<p ng-show="form.rangeDaily.$error.min" class="help-block">Daily range more than £5</p>
would become:
<p ng-show="form.forcedRangeDaily.$error.min" class="help-block">Forced Daily range more than £5</p>
ng-disabled="form.$invalid || promise" promise-btn="promise" ng-click="updateRange()">Update</button>
, ng-disabled="form.$invalid || promise" promise-btn="promise" ng-click="updateForcedRange()">Update</button>
Is there a way to avoid having two separate template files for the above? Could you please provide some examples, links, or pointers as to how that can be achieved?
Also, I see in the answers that a solution would be to add a boolean parameter inside the component and then call it twice. I am not sure how to call the component though. I have pasted my component below:
angular.module('app.investment.rangeDetails')
.component('pxForcedLimitAmount', {
templateUrl: '/assets/js/apps/range/range-details-dialog.tpl.html',
bindings: {
amount: '<',
isRequest: '<?',
requestedAt: '<?',
#Input() isForced: boolean //<<----I added this based on answers below
},
controller: [function () {
var ctrl = this;
ctrl.$onInit = function () {
ctrl.isRequest = ctrl.isRequest === true || false;
};
}],
});
Seems like only the placeholders need to change, so you can use a variable to decide what placeholder to display on the template. For example:
isForced: boolean;
ngOnInit() {
this.isForced = true; // decide how you want to toggle this
}
on the template:
<h5 *ngIf="!isForced">Update Range</h5>
<h5 *ngIf="isForced">Update Forced Range</h5>
and
<p *ngIf="!isForced" ng-show="form.rangeDaily.$error.min" class="help-block">
Daily range more than £5</p>
<p *ngIf="isForced" ng-show="form.forcedRangeDaily.$error.min" class="help-block">
Forced Daily range more than £5</p>
you can do the same for other tags as well.
From the comments, one way to "determine" the value for isForced is to introduce an input property to the component i.e.
#Input() isForced: boolean;
and invoke the component from elsewhere like:
<app-user [isForced]="true"></app-user>
You can use inputs.Write a component which takes input, and render it in html. then call this component in desired places with its selector
For events use output
See the doc https://angular.io/guide/inputs-outputs

AngularJs Filter: Generating notArray errors

I'm having an issue with a filter in my angularJS project.
We have a simple draft feature that allows users to save the contents of a large form as a JSON string in our database. They then can go to a section of the site to display and continue working on these forms. I want to provide them a filter on that page to filter by the date they saved the draft on.
Here is my markup:
<div ng-controller="savedFormCtrl" ng-cloak id="saved-form-wrapper"
class="border border-dark border-top-0 border-right-1 border-bottom-1
border-left-1 px-0" ng-init="getSavedForms()"
>
<!-- Search filters -->
<form name="savedFormsFilterWrapper" layout="row" flex="35" layout-align="end center" class="toolbar-search">
<!-- Date filter -->
<md-input-container flex="80">
<div class="text-light font-weight-bold float-left">Filter by saved date:</div>
<md-tooltip style="font-size:1em;">Filter your saved HRTF's</md-tooltip>
<md-datepicker name="dateFilter" class="hrtf-date savedFilterDatepicker"
md-date-locale="myLocale" data-ng-model="savedFormFilters" ng-blur="setFilterDate()"
md-placeholder="" md-open-on-focus
aria-label="Saved forms date filter">
</md-datepicker>
</md-input-container>
</form>
<!-- Saved forms body -->
<div id="savedFormAcc" class="accordion col-md-12 pt-3">
<!-- Accordion item Header -->
<div class="card" ng-repeat="item in savedForms | filter:savedFormFilters">
<div class="card-header savedFormItem" id="savedForm{{$index}}" data-toggle="collapse" data-target="#collapse{{$index}}">
<md-button class="md-raised md-primary" data-toggle="collapse"
data-target="#collapse{{$index}}" aria-expanded="false"
aria-controls="collapse{{index}}"
>
Form Saved on {{ item.savedOn }} - Click to expand
</md-button>
</div>
<!-- Accordion body continues on below -->
</div>
</div>
And my JS:
(function () {
'use strict';
angular.module('hrtf')
.controller('savedFormCtrl', ['$scope', '$window', 'formService',
function savedFormCtrl($scope, $window, formService) {
$scope.savedFormFilters = '';
//Get users's saved forms
$scope.savedForms = {};
$scope.getSavedForms = function(){
formService.getSavedForms()
.then(saved => {
saved.forEach( item =>{
item.data_json = JSON.parse(item.data_json);
});
$scope.savedForms = saved;
return $scope.savedForms;
};
}
]);
})();
Now, the filter works. But whenever The page is loaded, anywhere from 20-50 errors appear, all with the contents Error: [filter:notarray] Expected array but received: {}
All I need to do here is provide a simple filter on a string value to the parent objects savedOn: xxData Herexx value.
What am I doing wrong?
Turns out, I had a similar issue to this post
Basically, my ng-repeat and filter were initializing before the associated model could load. Initializing the model as a blank array and then creating the filter as part of the promise chain did the trick:
//Get users's saved forms
$scope.savedForms = [];
$scope.getSavedForms = function(){
formService.getSavedForms()
.then(saved => {
//Convert and format items here
$scope.savedForms = saved;
$scope.savedFormFilters = { savedOn: ''};
return $scope.savedForms;
}).catch(e => { console.error(e);
});
};
Pretty basic, but I'll leave it here in case it helps someone in the future :)

Filter Events with collections

I have projects and workflows with separate collections.
collections:
Project = new Meteor.Collection("project");
Workflow = new Meteor.Collection("workflow");
After creating project, I am selecting project in the form and displaying Work flow card.
HTML:
<!-- Workflow Card -->
<div id="wCard">
{{#each workflow}}
**<div class="pheader">
<h2>{{project}}</h2>
<hr width="100%">
</div>**
<br>
<div class="workflowcard">
<div class="module-card">
<div class="card-header wfmodule">{{workflowTitle}}</div>
<div class="casting">
<div class="assigned-tag">Assigned To:</div>
<div class="assigned-to">{{team}}<hr></div>
<div class="actions">No Actions Created</div>
</div>
<div class="due">
Due on:
<div>
<div class="day-stamp" >{{weekday d_date}}</div>
<div class="date-stamp">{{date d_date}}</div>
<div class="month-stamp">{{month d_date}}</div>
</div>
</div>
</div>
<div class="btn-box showmuloption">
<button type="edit" class="editw" style="display:none">Edit Workflow</button>
<button type="hide" class="hidew" style="display:none">Hide Actions</button>
<div class="actionBtn"><button class="btn-wf stage-blue-wf button-x-small-wf" id="newAction">New Action</button></div>
</div>
</div>
{{/each}}
</div>
.JS:
Template.workflow.helpers({
getWorkflow: function(){
return Workflow.find();
},
user: function(){
return Meteor.users.find({});
},
getNewaction: function(){
return Newaction.find();
},
});
Now the workflow cards are displaying in a list. I want to display workflows according to projects. If I select a project, the workflow should go to that project, and I create another workflow with same project, it should display in that project. And if I select another project, the workflow should display in that related project.
Please help through this.
I use reactive varrible for this.
At first you need add meteor package:
$ meteor add reactive-var
Your form with project selecting might look like this:
<select id="project" value="{{getProject}}">
{{#each getProjectsList}}
<option id='{{_id}}' value="{{_id}}">{{projectname}}</option>
{{/each}}
</select>
Don't forget create helpers getProject and getProjectsList.
Now it's time for reactive varrible, you can initialise this in onCreated event.
Template.workflow.onCreated(function() {
var instance = this;
instance.projectId = new ReactiveVar(null);
});
When you change selected project, you should change you reactive varrible.
Template.workflow.events({
'change #project': function(){
Template.instance().projectId.set($("#project").val());
});
Every workflow need to have field with project id. You get workflows list like this
{{#each getWorkflows}}
And there is helper for getting workflow's list:
Template.workflow.helpers({
getWorkflows: function() {
var id = Template.instance().projectId.get();
return Workflow.find({projectId: id});
},
...// another helpers
})
So, when you will change project in form, list of workflow also will change, because it depends on reactive var. Also you can sibscribe only needed workflow object, and not all collection.
I hope this will helpfull to someone.

Editing property from Object in ng-repeat from LocalStorage

I'm listing an array of objects saved into Localstorage in a table-like layout.
Each row displays data saved in a particular object. I want to be able to edit and update certain properties from the object once it has already been saved into LocalStorage.
This is how a couple of my objects looks like:
[{
"date":"2014 10 16",
"time":"20.22",
"car":"396",
"driver":"Seb",
"from":"A",
"destination":"B",
"pax":"3",
"arrival":"23.10"
},
{
"date":"2014 10 16",
"time":"23.22",
"car":"46",
"driver":"Eric",
"from":"C",
"destination":"E",
"pax":"3",
"arrival":"00.10"
}]
So far my frontend code displaying the Destination property looks like this:
HTML
<div class="col-md-3"
ng-show="editItem == false"
ng-hide="editItem">{{record.destination}}</div>
// Shows current value
<div class="col-md-3"
ng-show="editItem == true"
ng-hide="!editItem">
<select class="form-control"
ng-model="locationList2"
ng-options="location.place for location in locationlist | orderBy:'place'">
<option value="">Destination</option>
</select>
</div>
// Shows select with options to be picked to update property
<div class="col-md-1">
<button ng-click="editItem = !editItem"
ng-show="!editItem">Edit</button>
<button ng-click="editData(record); editItem = !editItem"
ng-show="editItem">Ok</button>
</div>
//Toggles between current value and select and triggers editData function
Relevant JS:
$scope.editData = function (record) {
record.destination = $scope.locationList2;
jsonToRecordLocalStorage($scope.recordlist);
}
So far when I trigger editData it just deletes the Destination property, it doesn't update it with the model of locationList2 from the Select.
What am I missing?
EDIT
Here's the complete ng-repeat piece of code:
<div class="row msf-row" ng-repeat="record in recordlist | filter: search">
<div class="col-md-1">{{record.time}}</div>
<div class="col-md-1"><strong>{{record.car}}</strong></div>
<div class="col-md-1">{{record.driver}}</div>
<div class="col-md-3">{{record.from}}</div>
<div class="col-md-3"
ng-show="editItem == false"
ng-hide="editItem">
{{record.destination}}
</div>
<div class="col-md-3"
ng-show="editItem == true"
ng-hide="!editItem">
<select class="form-control"
ng-model="locationList2"
ng-options="location.place for location in locationlist | orderBy:'place'">
<option value="">Destination</option>
</select>
</div>
<div class="col-md-1">{{record.pax}}</div>
<div class="col-md-1">
<button
ng-click="editItem = !editItem"
ng-show="!editItem">
<i class="fa fa-pencil"></i>
</button>
<button
ng-click="editData(record); editItem = !editItem"
ng-show="editItem">
<i class="fa fa-check"></i>
</button>
</div>
</div>
Also, I here's a Plunkr to ilustrate the issue!
Add a driver, car code and location before starting to see the app running and the mentioned problem.
You could use angular-local-storage as an abstraction over LocalStorage API.
If you want to just hack it, you can do something along localStorage.setItem('data', JSON.stringify(data)) when setting data and use JSON.parse(localStorage.getItem('data')) to extract it. LocalStorage doesn't deal with objects by default so we have to serialize it.
Regardless of the solution you choose, it could be a good idea to extend your edit a bit:
$scope.editData = function (recordlist) {
$scope.recordlist.destination = $scope.locationList2;
// replace whole LocalStorage data here now. no need to "patch" it
updateLocalStorage('data', <data containing your objects goes here>);
}
If you have multiple ways to modify the data and want to avoid explicit update, you could set up a watcher instead:
$scope.$watch(<data name goes here>, function(newVal) {
// update your LocalStorage now
});
Why it fails with ng-repeat?
The reason you see the behavior is quite simple. $scope.locationList2 is a single variable that gets bound for each member created by ng-repeat. That explains why it stays empty during edit.
You will need to bind the data using some other way. Consider binding it directly to your record models. Example: AngularJS - Using $index in ng-options .
Solution
The original code had bits like this:
JS:
$scope.editData = function (record) {
record.destination = $scope.location;
jsonToRecordLocalStorage($scope.recordlist);
};
HTML:
<select class="form-control" ng-model="location" ng-options="location.place for location in locationlist | orderBy:'place'">
<option value="">Destination</option>
</select>
Note that the markup is inside a ng-repeat and effectively each item created by it points at the same location! This isn't good.
To make it work I changed it like this:
JS:
$scope.editData = function () {
jsonToRecordLocalStorage($scope.recordlist);
};
HTML:
<select class="form-control" ng-model="record.destination" ng-options="location.place as location.place for location in locationlist | orderBy:'place'">
<option value="">Destination</option>
</select>
As mentioned above the JS could be replaced by a watcher. The important thing to note here is that I bind the data directly to the records. That avoid hassle at editData and more importantly gets rid of the problematic ng-model reference.

How can a data-bind to an element within a Kendo-Knockout listview?

I have a rather sophisticated template for Kendo ListView using knockout-kendo.js bindings. It displays beautifully. My problem is that I need to use the visible and click bindings in parts of the template, but I can't get them to work. Below is a simplified version of my template. Basically, deleteButtonVisible determines whether the close button can be seen, and removeComp removes the item from the array.
<div class='template'>
<div >
<div style='display:inline-block' data-bind='visible: deleteButtonVisible, event: {click: $parent.removeComp}'>
<img src='../../../Img/dialog_close.png'></img>
</div>
<div class='embolden'>#= type#</div><div class='label1'> #= marketArea# </div>
<div class='label2'> #= address# </div>
<!-- more of the same -->
</div>
The view model:
function CompViewModel() {
var self = this;
self.compData = ko.observableArray().subscribeTo("compData");
self.template = kendo.template(//template in here);
self.removeComp = function (comp) {
//do something here
}
}
html:
<div class="row" >
<div class="col-md-12 centerouter" id="compDiv" >
<div class="centerinner" id="compListView" data-bind="kendoListView: {data: compData, template: template}"></div>
</div>
</div>
finally, sample data:
{
type: "Comparable",
marketArea: "",
address: "2327 Bristol St",
deleteButtonVisible: true
},
Take in count that the deleteButtonVisible must be a property on the viewModel linked to the view.You are not doing that right now. The click element can v¡be access from the outer scope of the binding and remove the $parent.He take the method from the viewmodel. Take in count that every thing that you take on the vie must be present on the view model for a easy access.

Categories

Resources