Skip to content

Commit 0ecaa5d

Browse files
author
Miguel Gatmaytan
committed
SPA Sample Module Updates.
Added pagination, validation & responsiveness.
1 parent 6458acd commit 0ecaa5d

File tree

14 files changed

+277
-85
lines changed

14 files changed

+277
-85
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,4 @@ Thumbs.db
8484
Desktop.ini
8585

8686
Website/Install/Temp/
87+
Website/DesktopModules/
Binary file not shown.
Binary file not shown.

build/Module.build

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@
1717
<Images Include="Images/*.*" />
1818
<Keys Include="keys/*.*" />
1919
<ClientScripts Include="ClientScripts/*.*" />
20+
<Stylesheets Include="Stylesheets/*.*" />
2021
<JsFiles Include="js/*.*" />
2122
<Scripts Include="Scripts/*.*" />
2223
<SharedScripts Include="Scripts/Shared/*.*" />
2324
<Templates Include="Templates/*.htm" />
2425
<DataFiles Include="Resources/*.xml" />
25-
<Resources Include="@(ResourceFiles);@(Scripts);@(RootViews);@(Images);@(TextFiles);@(SourceFiles);@(ClientScripts);@(JsFiles);
26+
<Resources Include="@(ResourceFiles);@(Scripts);@(RootViews);@(Images);@(TextFiles);@(SourceFiles);@(ClientScripts);@(Stylesheets);@(JsFiles);
2627
@(Keys);@(Services);@(RootConfig);@(ConfigFiles);@(Templates);@(DataFiles);@(SharedScripts);@(Controls);@(Views);@(MVCViews); @(MVCConfig)" />
2728
</ItemGroup>
2829
</Target>
@@ -33,6 +34,7 @@
3334
<Copy SourceFiles="@(RootConfig)" DestinationFolder="$(ModuleFolderName)" />
3435
<Copy SourceFiles="@(ResourceFiles)" DestinationFolder="$(ModuleFolderName)\App_LocalResources" />
3536
<Copy SourceFiles="@(ClientScripts)" DestinationFolder="$(ModuleFolderName)\ClientScripts" />
37+
<Copy SourceFiles="@(Stylesheets)" DestinationFolder="$(ModuleFolderName)\Stylesheets" />
3638
<Copy SourceFiles="@(ConfigFiles)" DestinationFolder="$(ModuleFolderName)\Config" />
3739
<Copy SourceFiles="@(Controls)" DestinationFolder="$(ModuleFolderName)\Controls" />
3840
<Copy SourceFiles="@(Images)" DestinationFolder="$(ModuleFolderName)\Images" />

src/Dnn.ContactList.Api/ContactRepository.cs

+8-3
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,14 @@ public IPagedList<Contact> GetContacts(string searchTerm, int portalId, int page
103103
{
104104
Requires.NotNegative("portalId", portalId);
105105

106-
var contacts = GetContacts(portalId).Where(c => c.FirstName.Contains(searchTerm)
107-
|| c.LastName.Contains(searchTerm) || c.Email.Contains(searchTerm));
108-
106+
if (string.IsNullOrEmpty(searchTerm))
107+
{
108+
searchTerm = "";
109+
}
110+
var contacts = GetContacts(portalId).Where(c => c.FirstName.Contains(searchTerm)
111+
|| c.LastName.Contains(searchTerm) ||
112+
c.Email.Contains(searchTerm));
113+
109114

110115
return new PagedList<Contact>(contacts, pageIndex, pageSize);
111116
}

src/Dnn.ContactList.Spa/App_LocalResources/ContactList.resx

+9
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,15 @@
123123
<data name="Cancel.Text" xml:space="preserve">
124124
<value>Cancel</value>
125125
</data>
126+
<data name="ConfirmDeleteTitle.Text" xml:space="preserve">
127+
<value>Delete {0}</value>
128+
</data>
129+
<data name="Delete.Text" xml:space="preserve">
130+
<value>Delete</value>
131+
</data>
132+
<data name="DeleteConfirm.Text" xml:space="preserve">
133+
<value>Are you sure you want to delete this contact?</value>
134+
</data>
126135
<data name="Edit.Text" xml:space="preserve">
127136
<value>Edit Contact</value>
128137
</data>

src/Dnn.ContactList.Spa/ClientScripts/contacts.js

+74-18
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,34 @@ if (typeof contactList === 'undefined' || contactList === null) {
22
contactList = {};
33
};
44

5+
ko.extenders.required = function (target, options) {
6+
//add some sub-observables to our observable
7+
target.hasError = ko.observable();
8+
target.validationMessage = ko.observable();
9+
target.validationClass = ko.observable();
10+
11+
var regEx = new RegExp(options.regEx);
12+
//define a function to do validation
13+
function validate(newValue) {
14+
target.hasError(regEx.test(newValue) && newValue !== "" ? false : true);
15+
target.validationClass(regEx.test(newValue) && newValue !== "" ? "form-control" : "form-control has-error");
16+
target.validationMessage(regEx.test(newValue) && newValue !== "" ? "" : options.overrideMessage || "This field is required");
17+
}
18+
19+
//validate whenever the value changes
20+
target.subscribe(validate);
21+
22+
//return the original observable
23+
return target;
24+
};
25+
26+
function clearErrors(obsArr) {
27+
for (var i = 0; i < obsArr.length; i++) {
28+
obsArr[i].hasError(false);
29+
obsArr[i].validationClass("form-control");
30+
}
31+
}
32+
533
contactList.contactsViewModel = function(config) {
634
var self = this;
735
var resx = config.resx;
@@ -20,6 +48,8 @@ contactList.contactsViewModel = function(config) {
2048
self.isFormEnabled = ko.observable(config.settings.isFormEnabled);
2149
self.isEditMode = ko.observable(false);
2250
self.contacts = ko.observableArray([]);
51+
self.totalResults = ko.observable(preloadedData.pageCount);
52+
self.pageIndex = ko.observable(0);
2353

2454
self.selectedContact = new contactList.contactViewModel(self, config);
2555

@@ -30,6 +60,7 @@ contactList.contactsViewModel = function(config) {
3060
self.addContact = function(){
3161
toggleView();
3262
self.selectedContact.init();
63+
clearErrors([self.selectedContact.firstName, self.selectedContact.lastName, self.selectedContact.phone, self.selectedContact.email]);
3364
};
3465

3566
self.closeEdit = function() {
@@ -67,12 +98,16 @@ contactList.contactsViewModel = function(config) {
6798

6899
self.getContacts = function () {
69100
var params = {
101+
pageSize: self.pageSize,
102+
pageIndex: self.pageIndex(),
103+
searchTerm: ""
70104
};
71105

72106
util.contactService().get("GetContacts", params,
73107
function(data) {
74108
if (typeof data !== "undefined" && data != null && data.success === true) {
75109
//Success
110+
self.totalResults(data.data.totalCount);
76111
self.load(data.data);
77112
} else {
78113
//Error
@@ -95,6 +130,7 @@ contactList.contactsViewModel = function(config) {
95130
self.getContacts();
96131
}
97132
quickSettingsDispatcher.addSubcriber(moduleId, quickSettingsDispatcher.eventTypes.SAVE, updateView);
133+
pager.init(self, 5, self.refresh, resx);
98134
};
99135

100136
self.load = function(data) {
@@ -120,36 +156,49 @@ contactList.contactViewModel = function(parentViewModel, config) {
120156

121157
self.parentViewModel = parentViewModel;
122158
self.contactId = ko.observable(-1);
123-
self.firstName = ko.observable('');
124-
self.lastName = ko.observable('');
125-
self.email = ko.observable('');
126-
self.phone = ko.observable('');
159+
self.firstName = ko.observable('').extend({ required: { overrideMessage: "Please enter a first name" } });
160+
self.lastName = ko.observable('').extend({ required: { overrideMessage: "Please enter a last name" } });
161+
self.email = ko.observable('').extend({ required: { overrideMessage: "Please enter a valid email address", regEx: /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/ } });
162+
self.phone = ko.observable('').extend({ required: { overrideMessage: "Please enter a valid phone number in the format: 123-456-7890", regEx: /^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$/ } });
127163
self.twitter = ko.observable('');
128164

129-
self.cancel = function(){
165+
self.cancel = function () {
166+
clearErrors([self.firstName, self.lastName, self.email, self.phone]);
130167
parentViewModel.closeEdit();
131168
};
132169

133170
self.deleteContact = function (data, e) {
134-
var params = {
135-
contactId: data.contactId(),
136-
firstName: data.firstName(),
137-
lastName: data.lastName(),
138-
email: data.email(),
139-
phone: data.phone(),
140-
twitter: data.twitter()
141-
};
171+
var opts = {
172+
callbackTrue: function () {
173+
var params = {
174+
contactId: data.contactId(),
175+
firstName: data.firstName(),
176+
lastName: data.lastName(),
177+
email: data.email(),
178+
phone: data.phone(),
179+
twitter: data.twitter()
180+
};
142181

143182
util.contactService().post("DeleteContact", params,
144183
function(data){
145184
//Success
146185
parentViewModel.refresh();
147186
},
148187

149-
function(data){
150-
//Failure
151-
}
152-
);
188+
function (data) {
189+
//Failure
190+
}
191+
);
192+
},
193+
text: resx.DeleteConfirm,
194+
yesText: resx.Delete,
195+
noText: resx.Cancel,
196+
title: resx.ConfirmDeleteTitle.replace("{0}", data.firstName() + " " + data.lastName())
197+
};
198+
199+
$.dnnConfirm(opts);
200+
201+
153202
};
154203

155204
self.init = function(){
@@ -170,7 +219,14 @@ contactList.contactViewModel = function(parentViewModel, config) {
170219
self.twitter(data.twitter);
171220
};
172221

173-
self.saveContact = function(data, e) {
222+
self.saveContact = function (data, e) {
223+
self.firstName.valueHasMutated();
224+
self.lastName.valueHasMutated();
225+
self.phone.valueHasMutated();
226+
self.email.valueHasMutated();
227+
if ((self.firstName.hasError() || self.lastName.hasError() || self.email.hasError() || self.phone.hasError())) {
228+
return;
229+
}
174230
var params = {
175231
contactId: data.contactId(),
176232
firstName: data.firstName(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
var pager = {
2+
init: function (viewModel, pageSize, load, resx, pagerItemFormatDesc, nopagerItemFormatDesc) {
3+
viewModel.pageSize = pageSize;
4+
viewModel.pageIndex = ko.observable(0);
5+
6+
viewModel.startIndex = ko.computed(function () {
7+
return viewModel.pageIndex() * viewModel.pageSize + 1;
8+
});
9+
10+
viewModel.endIndex = ko.computed(function () {
11+
return Math.min((viewModel.pageIndex() + 1) * viewModel.pageSize, viewModel.totalResults());
12+
});
13+
14+
viewModel.currentPage = ko.computed(function () {
15+
return viewModel.pageIndex() + 1;
16+
});
17+
18+
viewModel.totalPages = ko.computed(function () {
19+
if (typeof viewModel.totalResults === 'function' && viewModel.totalResults())
20+
return Math.ceil(viewModel.totalResults() / viewModel.pageSize);
21+
return 1;
22+
});
23+
24+
viewModel.pagerVisible = ko.computed(function () {
25+
return viewModel.totalPages() > 1;
26+
});
27+
28+
viewModel.pagerItemsDescription = ko.computed(function () {
29+
return "Showing " + viewModel.startIndex() + " - " + viewModel.endIndex() + " of " + viewModel.totalResults() + " contacts";
30+
});
31+
32+
viewModel.pagerDescription = ko.computed(function () {
33+
return "Page: " + viewModel.currentPage() + " of " + viewModel.totalPages();
34+
});
35+
36+
viewModel.pagerPrevClass = ko.computed(function () {
37+
return 'prev' + (viewModel.pageIndex() < 1 ? ' disabled' : '');
38+
});
39+
40+
viewModel.pagerNextClass = ko.computed(function () {
41+
return 'next' + (viewModel.pageIndex() >= viewModel.totalPages() - 1 ? ' disabled' : '');
42+
});
43+
44+
viewModel.prev = function () {
45+
if (viewModel.pageIndex() <= 0) return;
46+
viewModel.pageIndex(viewModel.pageIndex() - 1);
47+
if (typeof load === 'function') load.apply(viewModel);
48+
};
49+
50+
viewModel.next = function () {
51+
if (viewModel.pageIndex() >= viewModel.totalPages() - 1) return;
52+
viewModel.pageIndex(viewModel.pageIndex() + 1);
53+
if (typeof load === 'function') load.apply(viewModel);
54+
};
55+
}
56+
};

src/Dnn.ContactList.Spa/Components/PreloadedDataPropertyAccess.cs

+9-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ public class PreloadedDataPropertyAccess : IPropertyAccess
2020
{
2121
private readonly IContactService _service;
2222
private readonly int _portalId;
23-
23+
private readonly string searchTerm;
24+
private readonly int pageIndex;
25+
private readonly int pageSize;
26+
2427
/// <summary>
2528
/// Default Constructor constructs a new PreloadedDataPropertyAccess
2629
/// </summary>
@@ -38,6 +41,9 @@ public PreloadedDataPropertyAccess(int portalId, IContactService service)
3841

3942
_service = service;
4043
_portalId = portalId;
44+
searchTerm = "";
45+
pageIndex = 0;
46+
pageSize = 5;
4147
}
4248

4349
/// <summary>
@@ -60,11 +66,11 @@ public virtual CacheLevel Cacheability
6066
/// <returns></returns>
6167
public string GetProperty(string propertyName, string format, CultureInfo formatProvider, UserInfo accessingUser, Scope accessLevel, ref bool propertyNotFound)
6268
{
63-
var contactList = _service.GetContacts(_portalId);
69+
var contactList = _service.GetContacts(searchTerm, _portalId, pageIndex, pageSize);
6470
var contacts = contactList
6571
.Select(contact => new ContactViewModel(contact))
6672
.ToList();
67-
return "{ results: " + JsonConvert.SerializeObject(contacts) + "}";
73+
return "{ results: " + JsonConvert.SerializeObject(contacts) + ", pageCount: " + contactList.TotalCount + "}";
6874
}
6975
}
7076
}

0 commit comments

Comments
 (0)