Vue.js - Pagination - pagination

I have the following pagination component.
If users adds remove items dynamically i.e via some ajax call, how do i ensure the correct active or disabled classes are applied on the pagination links?
For example if the user is currently on the last page which only has 1 item, if the user deletes that item, the pagination links re-render but then i lose the active disable class becuase that page no longer exists. i.e. the links should update to move the user to previous page.
<div class="comment-pager ">
<div class="panel panel-default panel-highlight no-border ">
<div class="panel-body padded-5">
<nav v-if="totalItems > pageSize">
<ul class="pagination">
<li v-bind:class="[currentPage == 1 ? disabled : '']">
<a v-on:click.prevent="previous()" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<li v-bind:class="[currentPage == pages ? active : '']" v-for="page in pages" v-on:click.prevent="changePage(page)">
<a>{{ page }}</a>
</li>
<li v-bind:class="[currentPage == pages.length ? disabled : '']">
<a v-on:click.prevent="next()" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['totalItems', 'pageSize']
data: function () {
return {
currentPage: 1,
pages: [],
}
},
watch: {
totalItems: function () {
var pagesCount = Math.ceil(this.totalItems / this.pageSize);
this.pages = [];
for (var i = 1; i <= pagesCount; i++)
this.pages.push(i);
}
},
methods: {
changePage: function (page){
this.currentPage = page;
this.$emit('pageChanged', page);
}
previous: function (){
if (this.currentPage == 1)
return;
this.currentPage--;
this.$emit('pageChanged', this.currentPage);
}
next: function () {
if (this.currentPage == this.pages.length)
return;
this.currentPage++;
this.$emit('pageChanged', this.currentPage);
}
},
}
</script>
<paginator v-bind:total-items="totalItems" v-bind:page-size="query.pageSize" v-on:pageChanged="onPageChange"></paginator>

There is no complete equivalent to ngOnChanges() in vue.
ngOnChanges() is a lifecycle hook which takes in an object that maps each changed property name to a SimpleChange object holding the current and previous property values.
If you want the lifecycle hook that gets invoked after every change in data and before re-rendering the virtual DOM then you should be using beforeUpdate() hook.
But as in ngOnChanges() you can't get the hold of which property is updated or what is it's oldvalue or newValue is.
As mklimek answered you can set up watcher on the properties you want to watch for changes.
In watcher you get what the oldValue is and what it's changed new value is
new Vue({
el:'#app',
data:{
prop1: '',
prop2: '' // property to watch changes for
},
watch:{
prop#(newValue, oldValue){
console.log(newValue);
console.log(oldValue);
}
}
});
EDIT
For your case you do not need a watcher. You can setup the pages[] property as a computed property:
computed:{
pages(){
var pageArray = [];
var pagesCount = Math.ceil(this.totalItems / this.pageSize);
for (var i = 1; i <= pagesCount; i++)
pages.push(i);
}
return pageArray;
}
computed properties are cached based on their dependencies. A computed property will only re-evaluate when some of its dependencies have changed in your case the props
totalItems and pageSize
Now you can use the pages computed property as normal data property

You probably want to use watch property of a Vue instance.
var vm = new Vue({
data: {
count: 1
},
watch: {
count: function (val, oldVal) {
console.log('new: %s, old: %s', val, oldVal)
}
})

Related

Display single item with Vue.js

I have a list of items where the title is a link to display a detailed view of the item. Click the title and it correctly goes to url + Id. In the Vue tolls the detail page retrieves the item with matching ID but as and array not an object and the template does not display any properties - what am I missing?
<script>
import axios from "axios";
export default {
name: "Report",
data() {
return {
report: {}
};
},
mounted: function() {
this.getReport();
},
methods: {
getReport() {
let uri = "http://localhost:5000/api/reports/" + this.$route.params.id;
axios.get(uri).then(response => {
this.report = response.data;
});
}
}
};
</script>
The template is so
<template>
<v-content>
<h1>report detail page</h1>
<p>content will go here</p>-
<h3>{{ report.month }}</h3>
<pre>{{ report._id }}</pre>
</v-content>
</template>
any comments appreciated
url + Id
It sounds like your issue is that you are receiving an array not an object.
You can pull out objects encapsulated inside arrays easily.
For example, if we had the following data:
var bus1 = {passengers:10, shift:1}
var busArr = [bus1]
which we can assert: busArr === [{passengers:10, shift:1}]
We could then pull out bus1 by referencing the index 0:
var bus1New = busArr[0]
If you want to avoid the data transformation and just output the structure you can consider a v-for in your template.
<p v-for="val in report">
_id: {{val._id}}
<br>
month: {{val.month}}
</p>

how to create live search with knockout?

I have project where we need to create a live search with knockout, map with some titles, when the title enter in search field other titles should disappear, I try to use different code but non of them work, and there is no error to help me figure out.
my code for html :
Filter:
<ul data-bind="foreach: locations">
<li data-bind="text: title"></li>
</ul>
</div>
And for js:
function ViewModel(){
var self =this;
this.filter = ko.observable();
this.locations = ko.observableArray([{ title:'Safa Bridge'},{ title:'Holy Mosque'},{ title:'Diamond Tower'},{ title:'Albaik Resturant'},{ title:'Zamzam'}]);
this.visibleLocations = ko.computed(function(){
return this.locations().filter(function(location){
if(!self.filter() || location.title.toLowerCase().indexOf(self.filter().toLowerCase()) !== -1)
return location;
});
},this);
}
ko.applyBindings(new ViewModel());

Pagination meteor publish does not return records

I definitely know that something is wrong with this snippet but can't figure out the right way to get it done. I want to paginate the page where students are displayed. If I put it plainly like this return SchoolStudents.find();, it works perfectly by returning all the students but this defeats the main purpose of pagination. I'm either not sure where the problem is, either in the publish function or the helper function. What I want to achieve is that the records in SchoolStudents colleciton should be paginated to display 2 records on a page.
This is the autorun
Session.setDefault('skip', 0);
Template.view.onCreated(function () {
Session.setPersistent('ReceivedSlug', FlowRouter.getParam('myslug'));
this.autorun(function () {
Meteor.subscribe('SchoolStudents', Session.get('skip'));
});
});
this is the helper method
students(){
let myslug = trimInput(Session.get('ReceivedSlug'));
if (myslug) {
let mySchoolDoc = SchoolDb.findOne({slug: myslug});
if (mySchoolDoc) {
let arrayModuleSchool = StudentSchool.find({schoolId: mySchoolDoc._id});
if (arrayModuleSchool) {
var arrayStudentIds = [];
arrayModuleSchool.forEach(function(studentSchool){
arrayStudentIds.push(studentSchool.studentId);
});
let subReadiness = SchoolStudents.find({_id: {$in: arrayStudentIds}}).fetch();
if (subReadiness) {
return subReadiness;
}
}
}
}
}
This is the publish method
Meteor.publish('SchoolStudents', function (skipCount) {
check(skipCount, Number);
user = Meteor.users.findOne({_id:this.userId})
if(user) {
if(user.emails[0].verified) {
return SchoolStudents.find({userId: this.userId}, {limit: 2, skip: skipCount});
} else {
throw new Meteor.Error('Not authorized');
return false;
}
}
});
Blaze template
<section class="tab-section" id="content4">
{{#each student in students}}
<div class="row" style="margin-top: -20px;">
<!-- Begin Listing: 609 W GRAVERS LN-->
<div class="brdr bgc-fff pad-10 box-shad btm-mrg-20 property-listing card-1">
<div class="media">
<div class="media-body fnt-smaller">
<h4 class="media-heading">{{student.firstname}} {{student.lastname}}</h4>
<p class="hidden-xs" style="margin-bottom: 5px; margin-top: -10px;">{{trimString student.useremail 0 110}}</p><span class="fnt-smaller fnt-lighter fnt-arial">{{course.createdAt}}</span>
</div>
</div>
</div><!-- End Listing-->
</div>
{{/each}}
<ul class="pager">
<li class="studentprevious">Previous </li>
<li class="studentnext">Next </li>
</ul>
</section>
the pagination event
'click .studentprevious': function () {
if (Session.get('skip') > 0 ) {
Session.set('skip', Session.get('skip') - 2 );
}
},
'click .studentnext': function () {
Session.set('skip', Session.get('skip') + 2 );
}

Unobtrusive validation with Jquery Steps Wizard

Recently I asked a question for how to customize the JQuery Steps as I wanted to use partial views instead of static content. I have partially solved that problem by using the following code supported by jquery-steps,
<h3>Step 1</h3>
<section data-mode="async" data-url="/Formation/RenderStep1"></section>
<h3>Step 2</h3>
<section data-mode="async" data-url="/Formation/RenderStep2"></section>
Now the big problem I am facing right now is how to use unobtrusive validation. I don't want to use JQuery custom validation and there must be some way of using Obtrusive with it.
Each partial view that is rendered has its own form. I want to validate the form in the onStepChanging function of jquery-steps,
$("#my-steps").steps({
headerTag: "h3",
bodyTag: "section",
contentMode: "async",
transitionEffect: "fade",
stepsOrientation: "vertical",
onStepChanging: function (event, currentIndex, newIndex) {
return true;
}
});
I have tried calling $.validator.unobtrusvie.parse('#myform'); in the onStepChanging function but ('#myform') is undefined and still I don't know that whether this is the right way to call the unobtrusive validation manually. Kindly guide me and show me the direction to achieve this. Any help will be highly appreciated.
It sounds like your trying manage multiple forms within the JQuery Steps library and I don't think that is what its intended for.
When you configure JQuery Steps, you set it up against the form in your view.
Unobtrusive JQuery Validation is looking at the model in your view and automatically configuring the HTML with the relevant data attributes for error handling.
This validation should be firing at the client side automatically.
There shouldn't be a problem with using Partial View's, as long as there encapsulated within the same form element.
What is the requirement to have each partial view wrapped in its own form? If your trying to make multiple posts throughout the JQuery Steps form wizard, your defeating the object.
At each step in the JQuery Steps form, your only validating the one form like this :-
onStepChanging: function (event, currentIndex, newIndex) {
//Allways allow user to move backwards.
if (currentIndex > newIndex) {
return true;
}
// Remove the validation errors from the next step, incase user has previously visited it.
var form = $(this);
if (currentIndex < newIndex) {
// remove error styles
$(".body:eq(" + newIndex + ") label.error", form).remove();
$(".body:eq(" + newIndex + ") .error", form).removeClass("error");
}
//disable validation on fields that are disabled or hidden.
form.validate().settings.ignore = ":disabled,:hidden";
return form.valid();
}
Once the user has finished entering data, and the client side validation has been met, you hook into the onFinished method and post the form :-
onFinished: function (event, currentIndex) {
var form = $(this);
form.submit();
}
The purpose of JQuery Steps is to allow the user to have a fluid experience of filling out a form and to not be overwhelmed with the number of questions been asked.
From the developers perspective, it enables us to split up the form into nice size-able chunks without having to worry about saving progress between screens or losing the state of the form data and allows us to capture all of the required data with only having to make that one post once all validation criteria has been met.
I tried the formvalidation plugin, it will relax your mind from searching in validation without form tag or validation without submit the form that's the issue I solved when I tried it.
I know it's not free but you can try it from here, personally I like it
First update height after validation
<style type="text/css">
/* Adjust the height of section */
#profileForm .content {
min-height: 100px;
}
#profileForm .content > .body {
width: 100%;
height: auto;
padding: 15px;
position: relative;
}
Second, add data-steps index to your section*
<form id="profileForm" method="post" class="form-horizontal">
<h2>Account</h2>
<section data-step="0">
<div class="form-group">
<label class="col-xs-3 control-label">Username</label>
<div class="col-xs-5">
<input type="text" class="form-control" name="username" />
</div>
</div>
<div class="form-group">
<label class="col-xs-3 control-label">Email</label>
<div class="col-xs-5">
<input type="text" class="form-control" name="email" />
</div>
</div>
<div class="form-group">
<label class="col-xs-3 control-label">Password</label>
<div class="col-xs-5">
<input type="password" class="form-control" name="password" />
</div>
</div>
<div class="form-group">
<label class="col-xs-3 control-label">Retype password</label>
<div class="col-xs-5">
<input type="password" class="form-control" name="confirmPassword" />
</div>
</div>
</section>
Third, javascript code
<script>
## // to adjust step height to fit frame after showing validation messages##
$(document).ready(function() {
function adjustIframeHeight() {
var $body = $('body'),
$iframe = $body.data('iframe.fv');
if ($iframe) {
// Adjust the height of iframe
$iframe.height($body.height());
}
}
// IMPORTANT: You must call .steps() before calling .formValidation()
$('#profileForm')
// setps setup
.steps({
headerTag: 'h2',
bodyTag: 'section',
onStepChanged: function(e, currentIndex, priorIndex) {
// You don't need to care about it
// It is for the specific demo
adjustIframeHeight();
},
// Triggered when clicking the Previous/Next buttons
// to apply validation to your section
onStepChanging: function(e, currentIndex, newIndex) {
var fv = $('#profileForm').data('formValidation'), // FormValidation instance
// The current step container
$container = $('#profileForm').find('section[data-step="' + currentIndex +'"]');
// Validate the container
fv.validateContainer($container);
var isValidStep = fv.isValidContainer($container);
if (isValidStep === false || isValidStep === null) {
// Do not jump to the next step
return false;
}
return true;
},
// Triggered when clicking the Finish button
onFinishing: function(e, currentIndex) {
var fv = $('#profileForm').data('formValidation'),
$container = $('#profileForm').find('section[data-step="' + currentIndex +'"]');
// Validate the last step container
fv.validateContainer($container);
var isValidStep = fv.isValidContainer($container);
if (isValidStep === false || isValidStep === null) {
return false;
}
return true;
},
onFinished: function(e, currentIndex) {
// Uncomment the following line to submit the form using the defaultSubmit() method
// $('#profileForm').formValidation('defaultSubmit');
// For testing purpose
$('#welcomeModal').modal();
}
})
.formValidation({
framework: 'bootstrap',
icon: {
valid: 'glyphicon glyphicon-ok',
invalid: 'glyphicon glyphicon-remove',
validating: 'glyphicon glyphicon-refresh'
},
// This option will not ignore invisible fields which belong to inactive panels
excluded: ':disabled',
fields: {
username: {
validators: {
notEmpty: {
// for asp.net i used element attribute to integerated with unobtrusive validation
// message :$('username').attr('data-val-required')
message: 'The username is required'
},
stringLength: {
min: 6,
max: 30,
message: 'The username must be more than 6 and less than 30 characters long'
},
regexp: {
regexp: /^[a-zA-Z0-9_\.]+$/,
message: 'The username can only consist of alphabetical, number, dot and underscore'
}
}
},
email: {
validators: {
notEmpty: {
message: 'The email address is required'
},
emailAddress: {
message: 'The input is not a valid email address'
}
}
},
password: {
validators: {
notEmpty: {
message: 'The password is required'
},
different: {
field: 'username',
message: 'The password cannot be the same as username'
}
}
},
confirmPassword: {
validators: {
notEmpty: {
message: 'The confirm password is required'
},
identical: {
field: 'password',
message: 'The confirm password must be the same as original one'
}
}
}
}
});

Angularjs jQuery FIle Upload

I'm new at Angularjs and I'm trying to create an AngularJS project with jQuery File Upload but I could not distinguish between directives file controllers file and the view.
Can anyone help me by providing me a clear structure of how files should be placed? (controllers, directives, and view)
I wrote something for my very first Angular.js project. It's from before there was an Angular.js example, but if you want to see the hard way, you can have it. It's not the best, but it may be a good place for you to start. This is my directives.js file.
(function(angular){
'use strict';
var directives = angular.module('appName.directives', []);
directives.directive('imageUploader', [
function imageUploader() {
return {
restrict: 'A',
link : function(scope, elem, attr, ctrl) {
var $imgDiv = $('.uploaded-image')
, $elem
, $status = elem.next('.progress')
, $progressBar = $status.find('.bar')
, config = {
dataType : 'json',
start : function(e) {
$elem = $(e.target);
$elem.hide();
$status.removeClass('hide');
$progressBar.text('Uploading...');
},
done : function(e, data) {
var url = data.result.url;
$('<img />').attr('src', url).appendTo($imgDiv.removeClass('hide'));
scope.$apply(function() {
scope.pick.photo = url;
})
console.log(scope);
console.log($status);
$status.removeClass('progress-striped progress-warning active').addClass('progress-success');
$progressBar.text('Done');
},
progress : function(e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
$progressBar.css('width', progress + '%');
if (progress === 100) {
$status.addClass('progress-warning');
$progressBar.text('Processing...');
}
},
error : function(resp, er, msg) {
$elem.show();
$status.removeClass('active progress-warning progress-striped').addClass('progress-danger');
$progressBar.css('width', '100%');
if (resp.status === 415) {
$progressBar.text(msg);
} else {
$progressBar.text('There was an error. Please try again.');
}
}
};
elem.fileupload(config);
}
}
}
]);
})(window.angular)
I didn't do anything special for the controller. The only part of the view that matters is this:
<div class="control-group" data-ng-class="{ 'error' : errors.image }">
<label class="control-label">Upload Picture</label>
<div class="controls">
<input type="file" name="files[]" data-url="/uploader" image-uploader>
<div class="progress progress-striped active hide">
<div class="bar"></div>
</div>
<div class="uploaded-image hide"></div>
</div>
</div>

Resources