on my angularjs application i show a modal window to edit an event and add/remove event dates(i use bootstrap datepicker and timepicker).
the event has some fixed dates, i dont have problem with that because i have them created first and i use them on ng-model (of the datepicker & timepicker).
the problem is when the user press the add button to add new event dates dynamically, i dont have date variable to assign to the ng-model of the datepicker .
what i do to achieve that:
inside .controller('ModalEditEventP4ctrl',.. i manipulate the modal window(edit event).here i have a an empty object that i use it to add new eventdates into the directive addNewDate.
$scope.datesObj = {}
the add new eventdate button is a directive, where i pass a dates array object from the controller. Inside the directive i create new dates objects and push them on the array, so as to assign it into the html template:
.directive('addNewDate', function($compile){
return {
restrict: 'AE',
scope: {
onClick: '&',
dyndatesObj: '='
},
link: function (scope, element, attrs) {
element.bind('click', function () {
/*1. here i create new date object and push it on the array */
scope.dyndatesObj.push({dynDateStart:new Date(),dynDateEnd:new Date(),dtStatus:'1'});
/*2. get the last item */
var items = $(".row.basicDates").length-1;
/*3. compile another directive 'newDateBlock'*/
/* and pass it into the DOM*/
/* the directive it is compiled but the datepickers are empty*/
$('.row.basicDates:eq('+items+')').append($compile("<new-date-block />")(scope));
scope.$apply();
});
}
}
})
the directive newDateBlock that has the DOM elements which i compile it through the ebove directive is this:
.directive('newDateBlock', function(){
return {
restrict: 'AE',
scope: {
onClick: '&',
myDate:'='
},
templateUrl: 'assets/modules/part4/templates/addNewDate.tpl.html',
link: function (scope, element, attrs) {
element.bind('click', function () {
console.log('inside directive');
});
}
}
});
the template file addNewDate.tpl.html(i dont show it all). everything works ok , except from the datepickers that ,although i assign them ng-model= datesObj[datesObj.length-1][dynDateStart], they are empty.
<div class="row" >
<div class="col-md-6">
<div class="row">
<div class="col-md-4" style="padding-left: 0;">
<label>Start Date</label>
<label>Start Time</label>
</div>
<div class="col-md-8" style="padding-left: 0;">
<p class="input-group">
<input type="text" class="form-control" style="width:100px"
datepicker-popup="{{format}}"
/*ng-model seems not to work*/
ng-model="datesObj[datesObj.length-1][dynDateStart]"
is-open=""
datepicker-options="dateOptions"
ng-required="true"
close-text="Close"/>
<span class="input-group-btn" style="float:left">
<button type="button" class="btn btn-default" ng-click="">
<i class="glyphicon glyphicon-calendar"></i>
</button>
</span>
</p>
<timepicker ng-model="" ng-change="changed()" hour-step="1" minute-step="10" show-meridian="false"></timepicker>
</div>
</div>
it seems that the value inside the ng-model is not compiled , i dont know exactly what is the problem. any help would be appreciated.
to be more specific, i get the error (on the browser) that says:
TypeError: Cannot read property 'initDate' of undefined
at link (http://.../bower_components/angular-bootstrap/ui-bootstrap-tpls.min.js:8:23435)
at http://.../bower_components/angular/angular.min.js:70:141
at $ (http://.../bower_components/angular/angular.min.js:70:197)
at B (http://..../bower_components/angular/angular.min.js:59:255)
at g (http://..../bower_components/angular/angular.min.js:51:335)
at g (http://..../bower_components/angular/angular.min.js:51:352)
at g (http:/..../bower_components/angular/angular.min.js:51:352)
at g (http://.../bower_components/angular/angular.min.js:51:352)
at g (http://..../bower_components/angular/angular.min.js:51:352)
at g (http://.../bower_components/angular/angular.min.js:51:352) <input type="text" class="form-control ng-pristine ng-untouched ng-valid ng-isolate-scope" style="width:100px" datepicker-popup="{{format}}" ng-model="datesObj[0].dynDateStart" is-open="" datepicker-options="dateOptions" ng-required="true" close-text="Close">
After many hours and too many tests i quited the directives option and i made it work with functions inside the controller where i $compile the whole html template each time i add new date ,with ng-repeat , like refresh the whole DOM, i just keep the html template because it works perfect.
what i did:
1. inside my controller i created the addNewDate() function:
$scope.addNewDate = function(){
//i push the new item inside my events.eventDates object and not in another array object as previously
$scope.eventmodal.eventDates.push({eventStartDate:new Date(),eventEndDate:new Date(),dateStatus:'1'});
$templateRequest('/assets/modules/part4/templates/addNewDate.tpl.html').then(function(html){
// Convert the html to an actual DOM node
var template = angular.element(html);
// clear completely(not append) the previous block of dates and add the new ones
$('.basicDatesBlock').html(template);
// finally $compile the template and show it.
$compile(template)($scope);
});
};
2. my addNewDate.tpl.html i use the ng-repeat to show all the eventDates (the old + the new ones) like this:
<div class="row basicDates" ng-repeat="eventdate in eventmodal.eventDates track by $index" ng-show="eventdate.dateStatus == 1">
<!-- start date -->
<div class="col-md-6">
<div class="row">
<div class="col-md-4" style="padding-left: 0;">
<label>Start Date*</label>
<label>Start Time</label>
</div>
<div class="col-md-8" style="padding-left: 0;">
<p class="input-group">
<input type="text" class="form-control" style="width:100px"
datepicker-popup="{{format}}"
ng-model="eventdate.eventStartDate"
is-open="openIndex[$index]"
datepicker-options="dateOptions"
ng-required="true"
close-text="Close"/>
<span class="input-group-btn" style="float:left">
<button type="button" class="btn btn-default" ng-click="openme($index)">
<i class="glyphicon glyphicon-calendar"></i>
</button>
</span>
</p>
<timepicker ng-model="eventdate.eventStartDate" ng-change="changed()" hour-step="1" minute-step="10" show-meridian="false"></timepicker>
</div>
</div>
</div>
<!-- end date -->
<div class="col-md-6">
<div class="row">
<div class="col-md-5" style="width:31.6%;padding-right:0;">
<label>End Date*</label>
<label>End Time</label>
</div>
<div class="col-md-7">
<p class="input-group">
<input type="text" class="form-control" style="width:100px"
datepicker-popup="{{format}}"
ng-model="eventdate.eventEndDate"
is-open="openIndex[$index]"
datepicker-options="dateOptions"
ng-required="true"
close-text="Close"/>
<span class="input-group-btn" style="float:left">
<button type="button" class="btn btn-default" ng-click="openme($index)">
<i class="glyphicon glyphicon-calendar"></i>
</button>
</span>
</p>
<!-- i show the '+' button only for the first row-->
<div style="float:right" ng-show="$index==0">
<div class="plusCircle" style="margin-top:15px">
<!--<div class="plusSymbol" add-new-date dyndates-obj = datesObj>+</div>-->
<div class="plusSymbol" ng-click="addNewBasicDate()">+</div>
</div>
</div>
<!-- i show the 'X' button only for 2nd row and greater-->
<div style="float:right" ng-show="$index>0">
<div class="plusCircle" style="margin-top:15px">
<div class="plusSymbol">x</div>
</div>
</div>
<timepicker ng-model="eventdate.eventEndDate" ng-change="changed()" hour-step="1" minute-step="10" show-meridian="false"></timepicker>
</div>
</div>
</div>
And that's it. i tried to make myself clear , i hope it helps other guys with similar problems.
Related
I created the following HTML form inside a jqxWindow widget for a Laravel project:
<div id="provinceWindow">
<div id="provinceWindowHeader"></div>
<div id="provinceWindowContent">
<form id="provinceForm" method="POST" action="{{route('province.store')}}">
{{ csrf_field() }}
<input type="hidden" name="provinceId" id="provinceId" value=""/>
<div class="form-group row">
<div class="col-6"><label>English province name</label></div>
<div class="col-6"><input type="text" name="provinceNameEn" id="provinceNameEn" maxlength="20"/></div>
</div>
<div class="form-group row">
<div class="col-6"><label>Spanish province name</label></div>
<div class="col-6"><input type="text" name="provinceNameSp" id="provinceNameSp" maxlength="20"/></div>
</div>
<br/>
<div class="form-group row justify-content-center">
<input type="button" value="Submit" id="submitBtn" class="btn btn-sm col-3" />
<span class="spacer"></span>
<input type="button" value="Cancel" id="cancelBtn" class="btn btn-sm col-3" />
</div>
</form>
</div>
</div>
This is the javascript file:
$(document).ready(function () {
var datarow = null;
$.ajaxSetup({
headers: {'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')}
});
//-----------------------------
// Window settings
//-----------------------------
$('#provinceWindow').jqxWindow({
autoOpen: false,
isModal: true,
width: 400,
height: 160,
resizable: false,
title: 'Province name',
cancelButton: $('#cancelBtn'),
initContent: function () {
$('#submitBtn').jqxButton();
$('#submitBtn').on('click', function () {
$('#provinceForm').submit();
});
}
}).css('top', '35%');
The file routes\web.php has only one resourse route defined for this page:
// Routes for province maintenance
Route::resource('province', 'provinceController');
Checking the available routes with php artisan route:list command, I get these:
Method URI Name Action
GET|HEAD / APP\Http\Controllers\homeController#index
GET|HEAD province province.index APP\Http\Controllers\provinceController#index
POST province province.store APP\Http\Controllers\provinceController#store
GET|HEAD province/create province.create APP\Http\Controllers\provinceController#create
GET|HEAD province/{province} province.show APP\Http\Controllers\provinceController#show
PUT|PATCH province/{province} province.update APP\Http\Controllers\provinceController#update
DELETE province/{province} province.destroy APP\Http\Controllers\provinceController#destroy
GET|HEAD province/{province}/edit province.edit APP\Http\Controllers\provinceController#edit
My controller action:
public function store(Request $request)
{
$fields = $request->all();
if ($request->provinceId == '') {
$province = new province($fields);
$validator = Validator::make($fields, $province->rules());
if ($validator->fails()) {
return redirect('')->withErrors($validator)->withInput();
}
else {
$province->save();
}
return view('province/index');
}
}
The form is shown on top of a jqxGrid widget, as a modal window, in order to capture the required information and perform the CRUD operations for the corresponding DB table.
The problem is, when I click the "Submit" button the window is closed and nothing else happens. The form is not posted to the indicated action and the data entered get lost.
It does not matter if I initialize the submitBtn inside the initContent or outside of it. The form is never posted.
I also tried the Close event of the jqxWindow to no avail.
If I take a look to the generated HTML it looks like this:
<form id="provinceForm" method="POST" action="http://mis:8080/province">
<input type="hidden" name="_token" value="Y9dF5PS7nUwFxHug8Ag6PHgcfR4xgxdC43KCGm07">
<input type="hidden" name="provinceId" id="provinceId" value="">
<div class="form-group row">
<div class="col-6">
<label>English province name</label>
</div>
<div class="col-6">
<input type="text" name="provinceNameEn" id="provinceNameEn" maxlength="20">
</div>
</div>
<div class="form-group row">
<div class="col-6">
<label>Spanish province name</label>
</div>
<div class="col-6">
<input type="text" name="provinceNameSp" id="provinceNameSp" maxlength="20">
</div>
</div>
<br>
<div class="form-group row justify-content-center">
<input type="button" value="Submit" id="submitBtn" class="btn btn-sm col-3 jqx-rc-all jqx-button jqx-widget jqx-fill-state-normal" role="button" aria-disabled="false">
<span class="spacer"></span>
<input type="button" value="Cancel" id="cancelBtn" class="btn btn-sm col-3">
</div>
</form>
Pressing the submit button takes me to the home page and nothing gets added to the DB table.
I guess the issue has something to do with Laravel routes because I get no errors.
What is missing or what is wrong with this approach?
Any light is appreciated.
Alright, because I erroneously added in my Model a validation, that was not needed, for a column (there was already a constraint defined at the DB table level), it was not possible to fulfill the validator when saving a new record. I commented that line in my Model and, that was it!
protected $rules = array(
'provinceNameEn' => 'required|alpha|max:20',
'provinceNameSp' => 'required|alpha|max:20'
//'deleted' => 'required|in:M,F' // This is already validated in a DB constraint.
);
I noticed the control was returned to my home page but without any message or error, but if you pay attention to my controller it was precisely the behavior programmed. However, there is no implementation to display the error messages, so I added dd($validator); before that return statement. There I read the message and finally found the solution.
my below datepicker code is like below. Its working fine. But the datepicker popup opens when i click on the input field. But want that to be open once i click on the calendar icon. Please suggest me a way to achieve that.
<div class="col-sm-6 col-md-6 col-lg-6 padleft">
<div class="form-group">
<label for="exampleSelect1">Start Date
</label>
<div class="input-group" style="width:75%">
<div class="input-group-btn">
<input type="text"
class="form-control date"
id="dateFrom"
placeholder="From"
ng-click="model.dateFrom=true"
datepicker-popup="{{model.format}}"
ng-model="model.defect.startDate"
is-open="model.dateFrom"
datepicker-options="dateOptions"
date-disabled="model.disabled(date, mode)"
ng-required="true"
close-text="Close" my-date>
<span class="btn btn-default " ng-click="model.dateFrom=true"><i class="glyphicon glyphicon-calendar"></i> </span>
</div>
</div>
</div>
</div>
In My controller :
self.format = 'MM-dd-yyyy';
self.open = function($event) {
$event.preventDefault();
$event.stopPropagation();
self.opened = {};
self.opened[$event.target.id] = true;
// log this to check if its setting the log
console.log(self.opened);
};
Angular : 1.6
Using 'ui.bootstrap.datetimepicker',
and 'ui.bootstrap
You'll need to add ng-click="model.dateFrom=true" as an attribute to the <i> element containing your calendar icon, not its parent span.
I would actually recommend writing a separate function for toggling the datepicker on and off. Something like
$scope.toggleDatePicker = function() {
$scope.model.dateFrom = !$scope.model.dateFrom;
};
and then calling that from your html: ng-click="toggleDatePicker()"
I have the date picker working when I set the id for the input tag like the following:
<div class="col-sm-4" id="datepicker">
<input type="text" class="form-control" id="inputDate" placeholder="Date">
<span class="glyphicon glyphicon-calendar form-control-feedback" aria-hidden="true"></span>
</div>
<script type="text/javascript">
$(function () {
$('#inputDate').datetimepicker();
});
</script>
I just wonder why its not working if I used the div instead of the input tag ( the datepicker id), as I saw some examples online using the div instead of the input.
It wont work if you don't set class input-group on <div>
Check it here https://jsfiddle.net/a9729dc9/
.datetimepicker(); init the input that will use the datepicker feature, that why we should call it on the concerned input field.
If you want to reference to the input through the div you could use it as :
$('#datepicker input').datetimepicker();
//Or also
$('#datepicker #inputDate').datetimepicker();
In bootstrap you have to add class .input-group to the parent (container) div so bootstrap could detect input-group-addon and init the picker, e.g :
<div class="input-group" id="datepicker">
<input type="text" class="form-control">
<div class="input-group-addon">
<span class="glyphicon glyphicon-th"></span>
</div>
</div>
Hope this helps.
I am not very familiar with the directives in the AngularJS as I'm used on relying on controllers. Is it possible to set validity on other inputs via directive? Here is the case, I have a button and when it is clicked.. It should set a validity on a certain input text. But I just can not seem to get the code that will set the validity.
Here is the HTML code:
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
</head>
<body>
<div class="container">
<div id="login-container">
<div class="form-group">
<span class="glyphicon glyphicon-user"></span>
</div>
<form name="loginForm" novalidate>
<div class="form-group">
dasdas -- {{ loginForm.student_code.$error.codeValidity }}
<input type="text" class="form-control text-center" name="student_code" ng-model="studentCode" placeholder="Enter Exam Code" required />
<span class="errors" id="error-student-code" ng-if="loginForm.student_code.$error.codeValidity">{{ errors }}</span>
</div>
</form>
<login-button form="loginForm"></login-button>
<button class="btn btn-primary">Register</button>
</div>
</div>
</body>
</html>
Here is the JS Code:
var app = angular.module("myApp", []);
app.directive('loginButton', loginButton);
loginButton.$inject = ["$http", "$window"];
function loginButton($http, $window){
return {
template: '<button type="button" class="btn btn-primary">Take Exam</button>',
link: function(scope, element, attrs, ctrl){
element.on('click', function(){
form = attrs.form;
form.student_code.$setValidity('codeValidity', true);
scope.errors = "Code is Invalid";
});
}
}
}
Here is a sample on plunker: http://plnkr.co/edit/kcdDgZpStQixTpGWqxOp?p=preview
PS.
I know this can be easily achieved using controllers but I would like to achieve this using directives as my practice to get used to it.
Yes, it is possible and you almost got it right. What you wanna do is pass form to isolated scope and then use in directive controller.
There is no need to add event manually, as you can use ng-click.
Otherwise you would need to use $scope.$apply.
In directives you can use linking function, but you don't have to.
In general it is good practice to keep light logic in controller and DOM manipulation in linking function.
var app = angular.module("myApp", []);
app.directive('loginButton', loginButton);
loginButton.$inject = ["$http", "$window"];
function loginButton($http, $window){
return {
template: '<button type="button" class="btn btn-primary" ng-click="click()">Take Exam</button>',
scope: {
form: '='
},
controller: function($scope){
$scope.click = function(){
form = $scope.form;
form.student_code.$setValidity('codeValidity', false);
};
}
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.js"></script>
<div ng-app="myApp">
<div class="container">
<div id="login-container">
<div class="form-group">
<span class="glyphicon glyphicon-user"></span>
</div>
<form name="loginForm" novalidate>
<div class="form-group">
dasdas -- {{ loginForm.student_code.$error }}<br>
<input type="text" class="form-control text-center" name="student_code" ng-model="studentCode" placeholder="Enter Exam Code" required />
<span class="errors" id="error-student-code" ng-if="errors">{{ errors }}</span>
</div>
</form>
<login-button form="loginForm"></login-button>
<button class="btn btn-primary">Register</button>
</div>
</div>
</div>
I have a form where a user can add dynamic bootstrap timepicker fields.
I'm using Bootstrap timepicker (https://github.com/eternicode/bootstrap-datepicker) for timepicker input fields that will be dynamically generated
The problem is, when clicking on any datepicker element (except the first one), all the changes happen to the first one only.
Here is the JSFIDDLE
The HTML part of my code is:
<div class="row multi-field-wrapper">
<div class="multi-fields">
<div class="multi-field">
<div class="form-group col-xs-12 col-sm-4">
<div class="input-group">
<input type="text" name="ticket-price" maxlength="5" class="form-control" placeholder="FREE" >
<span class="input-group-btn">
<button class="btn btn-default add-field" type="button">+</button>
</span>
<span class="input-group-btn">
<button class="btn btn-default remove-field" type="button">-</button>
</span>
</div>
</div>
<div class="row">
<div class="form-group col-xs-6 col-sm-3">
<label>Start Date</label>
<input type="text" name="tstart-date" class="form-control tstart-date-picker" placeholder="">
</div>
</div>
</div>
</div>
</div>
The problem is that you clone DOM elements with event handlers attached by jQuery. It causes troubles when you try to select date in new input, because events bound by datepicker somehow mixed up with original on from the first input.
The fix is pretty simple: clone without true argument, and also use delegated click event for adding new rows. The rest doesn't change:
$(this).on('click', '.add-field', function (e) {
$('.multi-field:first-child', $wrapper).clone().appendTo($wrapper);
$('.multi-field:last-child').find('input[name="tstart-date"]').val('');
$('.multi-field:last .tstart-date-picker').datepicker({
autoclose: true,
format: 'dd-M-yyyy',
todayHighlight: true,
});
});
Demo: http://jsfiddle.net/z2j6u8ro/2/
The datepicker must be assigned to each element specifically. Try this:
$(".add-field", $(this)).click(function(e) {
(CODE TO ADD NEW FIELD HERE)
$(.tstart-date-picker).each(function(){
$(this).datepicker("destroy");
$(this).datepicker({
(DATEPICKER OPTIONS HERE)
});
});
}