How to display ProgressView during an async operation in SwiftUI - asynchronous

Hello!
I want to display progress during my operation. I'm new to swift and swiftui. Please help...
Here is the model with one async method:
class CountriesViewModel : ObservableObject {
#Published var countries: [String] = [String]()
#Published var isFetching: Bool = false
#MainActor
func fetch() async {
countries.removeAll()
isFetching = true
countries.append("Country 1")
Thread.sleep(forTimeInterval: 1)
countries.append("Country 2")
Thread.sleep(forTimeInterval: 1)
countries.append("Country 3")
Thread.sleep(forTimeInterval: 1)
isFetching = false
}
}
and the ContentView:
struct ContentView: View {
#StateObject var countriesVM = CountriesViewModel()
var body: some View {
ZStack {
if (countriesVM.isFetching) {
ProgressView("Fetching...")
}
else {
List {
ForEach(countriesVM.countries, id: \.self) { country in
Text(country)
}
}
.task {
await countriesVM.fetch()
}
.refreshable {
Task {
await countriesVM.fetch()
}
}
}
}
}
}
The progress is not displayed. What am I doing wrong?

you could try this simple approach:
struct ContentView: View {
#StateObject var countriesVM = CountriesViewModel()
var body: some View {
ZStack {
if (countriesVM.isFetching) {
ProgressView("Fetching...")
}
else {
List {
ForEach(countriesVM.countries, id: \.self) { country in
Text(country)
}
}
.refreshable {
Task {
await countriesVM.fetch()
}
}
}
}
.task { // <-- here
await countriesVM.fetch()
}
}
class CountriesViewModel : ObservableObject {
#Published var countries: [String] = [String]()
#Published var isFetching: Bool = true // <-- here
#MainActor
func fetch() async {
countries.removeAll()
isFetching = true
countries.append("Country 1")
Thread.sleep(forTimeInterval: 1)
countries.append("Country 2")
Thread.sleep(forTimeInterval: 1)
countries.append("Country 3")
Thread.sleep(forTimeInterval: 1)
isFetching = false
}
}
}
EDIT-1: including a button to refresh the data.
struct ContentView: View {
#StateObject var countriesVM = CountriesViewModel()
var body: some View {
ZStack {
if countriesVM.isFetching {
ProgressView("Fetching...")
}
else {
VStack {
Button("refresh", action: {
Task { await countriesVM.fetch() }
}).buttonStyle(.bordered)
List {
ForEach(countriesVM.countries, id: \.self) { country in
Text(country)
}
}
.refreshable {
await countriesVM.fetch()
}
}
}
}
.task {
await countriesVM.fetch()
}
}
class CountriesViewModel : ObservableObject {
#Published var countries: [String] = [String]()
#Published var isFetching: Bool = false
#MainActor
func fetch() async {
countries.removeAll()
isFetching = true
// representative background async process
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 2) {
// eventualy, updates on the main thread for the UI to use
DispatchQueue.main.async {
self.countries.append("Country 1")
self.countries.append("Country 2")
self.countries.append("Country 3")
self.isFetching = false
}
}
}
}
}

Related

SwiftUI Firebase Phone Auth Change DisplayName

The displayName in the user parameter of the addStateDidChangeListener function in the listener function inside the SessionStore Class returns nil. But I am changing the displayName with the createProfileChangeRequest function. I have one problem. When I change the DisplayName, when I log in with the phone number for the first time, the displayName returns empty. However, when I log in with the application closed in the background, the displayName is full. What is the reason of this ? Where am I doing wrong?
Model:
struct User {
var uid: String
var displayName: String?
var phoneNumber: String?
var isAdmin: Bool
}
Session Store:
class SessionStore : ObservableObject {
var didChange = PassthroughSubject<SessionStore, Never>()
var handle: AuthStateDidChangeListenerHandle?
#Published var user: User?
#Published var uid: String = ""
#Published var errorMessage: String = ""
#Published var showAlert: Bool = false
#Published var isOpenHomePage: Bool = false
#Published var code: String = ""
func listener() {
handle = Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
self.user = User(uid: user.uid, displayName: user.displayName, phoneNumber: user.phoneNumber ?? "", isAdmin: false)
} else {
self.user = nil
}
}
}
func verifyPhoneNumber(phoneNumber: String) {
PhoneAuthProvider.provider().verifyPhoneNumber("+90\(phoneNumber)", uiDelegate: nil) { ID, error in
if error != nil {
self.errorMessage = error?.localizedDescription ?? ""
self.showAlert = true
print("erroroo: \(self.errorMessage)")
return
}
self.uid = ID ?? ""
}
}
func credentialProviderPhoneNumber() {
let credential = PhoneAuthProvider.provider().credential(withVerificationID: uid, verificationCode: code)
Auth.auth().signIn(with: credential) { result, error in
if error != nil {
self.errorMessage = error?.localizedDescription ?? ""
print("error mesajı: \(self.errorMessage)")
return
}
self.isOpenHomePage = true
}
}
func signOut () {
do {
try Auth.auth().signOut()
self.user = nil
} catch {
}
}
func unbind () {
if let handle = handle {
Auth.auth().removeStateDidChangeListener(handle)
}
}
func createProfileChangeRequest() {
let changeRequest = Auth.auth().currentUser?.createProfileChangeRequest()
changeRequest?.displayName = "Test 123"
changeRequest?.commitChanges(completion: { error in
print("hata: \(error?.localizedDescription)")
})
}
}
SignIn View:
struct SignInView: View {
#State var phoneNumber: String = ""
#State var code: String = ""
#State var didGetCode: Bool = false
#EnvironmentObject var session: SessionStore
var body: some View {
NavigationView {
VStack {
TextField("Telefon Numarası", text: $phoneNumber)
.padding(15)
.background(Color(UIColor.secondarySystemBackground))
.cornerRadius(15)
.padding(.horizontal)
.keyboardType(.numberPad)
.opacity(didGetCode ? 0.5 : 1)
.disabled(didGetCode ? true : false)
TextField("Kod", text: $code)
.padding(15)
.background(Color(UIColor.secondarySystemBackground))
.cornerRadius(15)
.padding(.horizontal)
.textContentType(.oneTimeCode)
.keyboardType(.numberPad)
.opacity(didGetCode ? 1 : 0.5)
.disabled(didGetCode ? false : true)
Button(action: {
if code == "" && phoneNumber != "" {
session.verifyPhoneNumber(phoneNumber: phoneNumber)
didGetCode = true
} else {
session.code = code
session.credentialProviderPhoneNumber()
session.createProfileChangeRequest()
}
}) {
Text(didGetCode ? "Doğrula" : "Kod Al")
}
NavigationLink(
destination: HomeView(),
isActive: session.user != nil ? .constant(true) : $session.isOpenHomePage,
label: {
Text("")
})
}
.onAppear {
session.listener()
}
}
}
}
Home View:
struct HomeView: View {
#EnvironmentObject var session: SessionStore
#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
Text("Hello User Id: \(session.user?.uid ?? "")")
Text("Tel No: \(session.user?.phoneNumber ?? "")")
Text("Display Name: \(session.user?.displayName ?? "")")
Button(action: {
session.signOut()
presentationMode.wrappedValue.dismiss()
}) {
Text("Çıkış Yap")
}
}
.onAppear {
session.createProfileChangeRequest() -> here
}
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
}
}

asp.net "put" method with angular js

im workin with my website and actually workin with 3 methods-put,post,delete. In my code i did two of them:delete and post and they are correct, but i have no idea how i can do put do edit my TODO list.
That's my code, if you can help me i'll be glad.
Have a nice day.
This is a service .js file
app.factory('reservationService', ReservationService);
function ReservationService($http) {
var CreateReservation = function (reservation) {
return $http.post("api/todo", reservation);
}
var EditReservation = function (id) {
return $http.put("api/todo/", + id);
}
var DeleteReservation = function (id) {
return $http.delete("api/todo/" + id);
}
var GetAll = function () {
return $http.get("api/todo");
}
return {
CreateReservation: CreateReservation,
EditReservation: EditReservation,
DeleteReservation: DeleteReservation,
GetAll: GetAll
}
}
this is a angular maincontroller code
app.controller('mainCtrl', mainCtrl);
function mainCtrl($scope, reservationService) {
$scope.list = [];
$scope.reservation = {};
getReservations();
InitDateTimePickers();
$scope.isSelected = function () {
return getSelectedItems().length > 0;
};
$scope.append = function (id) {
var editedItem = _.first(getSelectedItems());
$scope.reservation = editedItem;
reservationService.EditReservation(id).then(function (result) {
getReservations();
});
};
function getReservations() {
reservationService.GetAll().then(function (result) {
for (var i = 0; i < result.data.length; i++) {
result.data[i].timeFormatted = moment(result.data[i].Time, "HH:mm").format("hh:mm");
}
$scope.list = result.data;
});
}
function removeReservation(id) {
reservationService.DeleteReservation(id).then(function (result) {
getReservations();
});
}
$scope.add = function () {
reservationService.CreateReservation($scope.reservation).then(function (result) {
getReservations();
});
};
function InitDateTimePickers() {
$('#timepickerFrom').bootstrapMaterialDatePicker({ date: false, format: 'HH:mm' }).on('change', function (e, date) {
}); }
$scope.remain = function () {
var count = 0;
angular.forEach($scope.list, function (todo) {
count += todo.done ? 0 : 1;
});
return count;
};
var getSelectedItems = function () {
return _.filter($scope.list, function (n) {
return n.done;
});
}
$scope.archive = function () {
var itemsToRemove = getSelectedItems();
_.each(itemsToRemove, function (item) {
removeReservation(item.id)
});
};
$scope.edit = function () {
var editedItem = _.first(getSelectedItems());
$scope.reservation = editedItem;
}
};
You can pass entire entity to the server to update it:
var EditReservation = function (reservation) {
return $http.put("api/todo/" + reservation.id, reservation);
}
Also it seems that this string contains typo: return $http.put("api/todo/", + id);

How to return "then()" method in angularjs?

My case:
app.js:
let app = angular.module('myApp', []);
app.controller('login', function ($scope, $login) {
$scope.account = {};
$scope.loginForm_submit = function ($event, account) {
$event.preventDefault();
if ($login.isValid(account)) {
$login.submit(account);
// goal:
$login.submit(account).then(function () {
window.location = '/'
}, function (msg) {
console.log(msg)
});
}
};
});
login.js:
app.factory('$login', function () {
let o = {
isValid: function (x) {
let success = false;
// validating...
return success
},
submit: function (x) {
// prevent to force submitting
if (this.isValid(x)) {
let formData = new FormData(), xhttp = new XMLHttpRequest();
// appending data to 'formData' via 'x'...
xhttp.onreadystatechange = function () {
if (xhttp.readyState === XMLHttpRequest.DONE) {
let data = JSON.parse(xhttp.responseText);
if (data['Success']) {
// return then() with successCallback() function
} else {
let msg = data['ErrorMessage'];
// return then() with errorCallback() function
}
}
}
xhttp.open('POST', '/account/register');
xhttp.send(formData);
}
}
}
return o
});
data is an object like:
let data = {
'Success': false,
'ErrorMessage': 'Invalid login attempt.'
};
I want to return then() method after submitting to access result. How can I do that?
UPDATE:
In controller:
[HttpPost]
public async Task<ObjectResult> Login(LoginViewModel model)
{
IDictionary<string, object> value = new Dictionary<string, object>();
value["Success"] = false;
if (ModelState.IsValid)
{
// login
value["Success"] = true;
}
return new ObjectResult(value);
}
First of all, you should avoid using $ for your own functions.
About your problem, you need to use $q. And you should use what angular offers to you.
Let me give you this :
app.factory('loginFactory', function($q, $http) {
var ret = {
isValid: isValid,
submit: submit
}
return ret;
function isValid(x) {
// Your code ...
return false;
}
function submit(x) {
// x is your form data, assuming it's a JSON object
var deferred = $q.defer();
// Assuming you're posting something
$http.post('yoururl', x,{yourConfigAsAnObject: ''})
.then(function(success){
console.log(success.data);
deferred.resolve(success.data);
}, function(error) {
console.log(error);
deferred.reject(error);
});
return deferred.promise;
}
});
Now, in your controller, you can use
loginFactory.submit(yourParam).then(function(success){
// Your code
}, function(error) {
// Your code
});
app.factory('$login', function ($q) {
let o = {
isValid: function (x) {
let success = false;
// validating...
return success
},
submit: function (x) {
var d = $q.defer();
// prevent to force submitting
if (this.isValid(x)) {
let formData = new FormData(), xhttp = new XMLHttpRequest();
// appending data to 'formData' via 'x'...
xhttp.onreadystatechange = function () {
if (xhttp.readyState === XMLHttpRequest.DONE) {
let data = JSON.parse(xhttp.responseText);
if (data['Success']) {
// return then() with successCallback() function
d.resolve('success');
} else {
let msg = data['ErrorMessage'];
d.reject(msg);
// return then() with errorCallback() function
}
}
}
xhttp.open('POST', '/account/register');
xhttp.send(formData);
}
else {
d.reject('error');
}
return d.promise;
}
}
return o
});
dude,I made a sample function with promise
$q should be injected as dependency
class AppUserService {
constructor($http,CONFIG_CONSTANTS,$q, AuthService) {
this.API_URL = CONFIG_CONSTANTS.API_URL;
this.$http = $http;
this.$q = $q;
this.api_token = AuthService.api_token;
}
getAppUserList() {
const deferred = this.$q.defer();
this.$http.get(`${this.API_URL}/customer?api_token=${this.api_token}`)
.success(response => deferred.resolve(response))
.error(error => deferred.reject(error));
return deferred.promise;
}
}
its in ES6 form.
How to use:
AppUserService.getAppuserList().then(success => {
// code for success
},error => {
// code for error
})
submit: function (x) {
return $q(function (resolve, reject) {
// prevent to force submitting
if (this.isValid(x)) {
let formData = new FormData(), xhttp = new XMLHttpRequest();
// appending data to 'formData' via 'x'...
xhttp.onreadystatechange = function () {
if (xhttp.readyState === XMLHttpRequest.DONE) {
let data = JSON.parse(xhttp.responseText);
if (data['Success']) {
resolve(data);
// return then() with successCallback() function
} else {
let msg = data['ErrorMessage'];
reject(msg);
}
}
}
xhttp.open('POST', '/account/register');
xhttp.send(formData);
}
else
reject('x not valid');
}
}
}
But I recommended to use angular $http service.

Stop x-tags from capturing focus/blur events

I am trying to create a custom element which wraps tinyMCE functionality.
I have the following:-
(function(xtag) {
xtag.register('x-tinymce', {
lifecycle:{
created: tinymceCreate,
removed: tinymceDestroy
},
accessors: {
disabled: {
attribute: {
boolean: true
},
get: getDisabledAttribute,
set: setDisabledAttribute
}
}
});
function tinymceCreate(){
var textarea = document.createElement('textarea');
var currentElement = this;
currentElement.textAreaId = xtag.uid();
textarea.id = currentElement.textAreaId;
currentElement.appendChild(textarea);
currentElement.currentMode = 'design';
var complexConfig = {
selector: '#' + currentElement.textAreaId,
setup: editorSetup
}
tinymce.init(complexConfig)
.then(function thenRetrieveEditor(editors) {
currentElement.currentEditor = editors[0];
currentElement.currentEditor.setMode(currentElement.currentMode ? currentElement.currentMode : 'design');
});
function editorSetup(editor) {
editor.on('blur', function blur(event) {
editor.save();
document.getElementById(editor.id).blur();
xtag.fireEvent(currentElement, 'blur', { detail: event, bubbles: false, cancellable: true });
});
editor.on('focus', function focus(event) {
xtag.fireEvent(currentElement, 'focus', { detail: event, bubbles: false, cancellable: true });
});
editor.on('BeforeSetContent', function beforeSetContent(ed) {
if (ed.content)
ed.content = ed.content.replace(/\t/ig, ' ');
});
}
}
function tinymceDestroy() {
if (this.currentEditor)
tinymce.remove(this.currentEditor);
}
function getDisabledAttribute() {
return this.currentMode === 'readonly';
}
function setDisabledAttribute(value) {
if (value) {
this.currentMode = 'readonly';
}
else {
this.currentMode = 'design';
}
if (this.currentEditor) {
this.currentEditor.setMode(this.currentMode);
}
}
})(xtag);
Now, when I register a blur event, it does get called, but so does the focus event. I think that this is because focus/blur events are captured by x-tag by default. I don't want it to do that. Instead, I want these events fired when the user focusses/blurs tinymce.
I am using xtags 1.5.11 and tinymce 4.4.3.
Update 1
OK, the problem is when I call:-
xtag.fireEvent(currentElement, 'focus', { detail: event, bubbles: false, cancellable: true });
This caused the focus to be lost on the editor and go to the containing eleemnt (x-tinymce). To counter this, I modified my editorSetup to look like this:-
function editorSetup(editor) {
// // Backspace is not detected in keypress, so need to include keyup event as well.
// editor.on('keypress change keyup focus', function(ed) {
// $j("#" + editor.id).trigger(ed.type);
// });
var isFocusFromEditor = false;
var isBlurFromEditor = false;
editor.on('blur', function blurEvent(event) {
console.log("blurred editor");
if (!isFocusFromEditor) {
editor.save();
xtag.fireEvent(currentElement, 'blur', { detail: event, bubbles: false, cancellable: false });
}
else {
console.log('refocussing');
isFocusFromEditor = false;
editor.focus();
isBlurFromEditor = true;
}
});
editor.on('focus', function focusEvent(event) {
console.log("Focus triggered");
isFocusFromEditor = true;
xtag.fireEvent(currentElement, 'focus', { detail: event, bubbles: false, cancellable: false });
});
editor.on('BeforeSetContent', function beforeSetContent(ed) {
if (ed.content) {
ed.content = ed.content.replace(/\t/ig, ' ');
}
});
}
This stops the blur event from triggering, unfortuantely, now, the blur event does not get called when you leave the editing area.
This feels like a tinyMCE problem, just not sure what.
Here is a solution that catches focus and blur events at the custom element level.
It uses the event.stopImmediatePropagation() method to stop the transmission of the blur event to other (external) event listeners when needed.
Also, it uses 2 hidden <input> (#FI and #FO) controls in order to catch the focus when the tab key is pressed.
document.registerElement('x-tinymce', {
prototype: Object.create(HTMLElement.prototype, {
createdCallback: {
value: function() {
var textarea = this.querySelector('textarea')
var output = this.querySelector('output')
output.textContent = "state"
var self = this
self.textAreaId = this.dataset.id // xtag.uid();
textarea.id = self.textAreaId
var complexConfig = {
selector: '#' + self.textAreaId,
setup: editorSetup,
}
var FI = this.querySelector('#FI')
FI.addEventListener('focus', function(ev) {
if (ev.relatedTarget) {
var ev = new FocusEvent('focus')
self.dispatchEvent(ev)
} else {
var ev = new FocusEvent('blur')
self.dispatchEvent(ev)
focusNextElement(-1)
}
})
var FO = this.querySelector('#FO')
FO.addEventListener('focus', function(ev) {
if (!ev.relatedTarget) {
var ev = new FocusEvent('blur')
self.dispatchEvent(ev)
focusNextElement(1)
} else {
var ev = new FocusEvent('focus')
self.dispatchEvent(ev)
}
})
var focused = false
this.addEventListener('focus', function(ev) {
console.log('{focus} in ', this.localName)
if (!focused) {
focused = true
output.textContent = focused
self.editor.focus()
} else {
console.error('should not be here')
ev.stopImmediatePropagation()
}
})
this.addEventListener('blur', function(ev) {
console.log('{blur} in %s', this.localName)
if (focused) {
focused = false
output.textContent = focused
} else {
console.log('=> cancel blur')
ev.stopImmediatePropagation()
}
})
tinymce.init(complexConfig).then(function(editors) {
//self.currentEditor = editors[0]
})
//private
function focusNextElement(diff) {
//add all elements we want to include in our selection
var focussableElements = 'a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])'
if (document.activeElement) {
var focussable = Array.prototype.filter.call(document.querySelectorAll(focussableElements), function(element) {
//check for visibility while always include the current activeElement
return element.offsetWidth > 0 || element.offsetHeight > 0 || element === document.activeElement
})
var index = focussable.indexOf(document.activeElement)
focussable[index + diff].focus()
}
}
function editorSetup(editor) {
console.warn('editor setup')
self.editor = editor
editor.on('focus', function(event) {
if (!focused) {
var ev = new FocusEvent('focus')
self.dispatchEvent(ev)
}
})
editor.on('blur', function(event) {
//if ( focused )
{
var ev = new FocusEvent('blur')
self.dispatchEvent(ev)
}
})
}
}
},
detachedCallback: {
value: function() {
if (this.editor)
tinymce.remove(this.editor)
}
}
})
})
var xElem = document.querySelector('x-tinymce')
xElem.addEventListener('focus', function(ev) {
console.info('focus!')
})
xElem.addEventListener('blur', function(ev) {
console.info('blur!')
})
<script src="http://cdn.tinymce.com/4/tinymce.min.js"></script>
<input type="text">
<x-tinymce data-id="foo">
<output id=output></output>
<input type=text id=FI style='width:0;height:0;border:none'>
<textarea>content</textarea>
<input type=text id=FO style='width:0;height:0;border:none'>
</x-tinymce>
<input type="text">

Kendo UI bug with jQuery versions

I have a Kendo UI grid and a Kendo UI Window on the same page. The window contains form elements for record insertion, a record being represented by a row in the grid. But for reasons not known by me, when I opened the window and closed it again and then reopened, Kendo UI scaled it to be 100x smaller. I didn't want to hack the window, so I've looked for alternative solution.
I've used jQuery 1.7.2. I've updated jQuery to version 1.8.0. and window opening, closing and reopening worked. I was very happy until I realized that now the grid filters are not working. When I click on a grid filter, nothing happens, no popup, nothing. What is the cause of this and what would be the solution?
EDIT:
This is my code (I've replaced the values of the Urls). Grid filters are working with jQuery 1.7.2. and window reopen works with new versions of jQuery. Also, if I remove the sort hack, the grid filter popup still doesn't show up.
var hshflt = {};
var addWindow;
var editWindow;
var init = false;
//Sort Hack
/*
Changes all dataSources to case insensitive sorting (client side sorting).
This snipped enable case insensitive sorting on Kendo UI grid, too.
The original case sensitive comparer is a private and can't be accessed without modifying the original source code.
tested with Kendo UI version 2012.2.710 (Q2 2012 / July 2012).
*/
var CaseInsensitiveComparer = {
getterCache: {},
getter: function (expression) {
return this.getterCache[expression] = this.getterCache[expression] || new Function("d", "return " + kendo.expr(expression));
},
selector: function (field) {
return jQuery.isFunction(field) ? field : this.getter(field);
},
asc: function (field) {
var selector = this.selector(field);
return function (a, b) {
if ((selector(a).toLowerCase) && (selector(b).toLowerCase)) {
a = selector(a).toLowerCase(); // the magical part
b = selector(b).toLowerCase();
}
return a > b ? 1 : (a < b ? -1 : 0);
};
},
desc: function (field) {
var selector = this.selector(field);
return function (a, b) {
if ((selector(a).toLowerCase) && (selector(b).toLowerCase)) {
a = selector(a).toLowerCase(); // the magical part
b = selector(b).toLowerCase();
}
return a < b ? 1 : (a > b ? -1 : 0);
};
},
create: function (descriptor) {
return this[descriptor.dir.toLowerCase()](descriptor.field);
},
combine: function (comparers) {
return function (a, b) {
var result = comparers[0](a, b),
idx,
length;
for (idx = 1, length = comparers.length; idx < length; idx++) {
result = result || comparers[idx](a, b);
}
return result;
};
}
};
kendo.data.Query.prototype.normalizeSort = function (field, dir) {
if (field) {
var descriptor = typeof field === "string" ? { field: field, dir: dir} : field,
descriptors = jQuery.isArray(descriptor) ? descriptor : (descriptor !== undefined ? [descriptor] : []);
return jQuery.grep(descriptors, function (d) { return !!d.dir; });
}
};
kendo.data.Query.prototype.sort = function (field, dir, comparer) {
var idx,
length,
descriptors = this.normalizeSort(field, dir),
comparers = [];
comparer = comparer || CaseInsensitiveComparer;
if (descriptors.length) {
for (idx = 0, length = descriptors.length; idx < length; idx++) {
comparers.push(comparer.create(descriptors[idx]));
}
return this.orderBy({ compare: comparer.combine(comparers) });
}
return this;
};
kendo.data.Query.prototype.orderBy = function (selector) {
var result = this.data.slice(0),
comparer = jQuery.isFunction(selector) || !selector ? CaseInsensitiveComparer.asc(selector) : selector.compare;
return new kendo.data.Query(result.sort(comparer));
};
kendo.data.Query.prototype.orderByDescending = function (selector) {
return new kendo.data.Query(this.data.slice(0).sort(CaseInsensitiveComparer.desc(selector)));
};
//Sort Hack
$("#refresh-btn").click(function () {
refreshGrid();
});
var grid;
function getPageIndex() {
if (!(grid)) {
return 0;
}
return grid.pager.page() - 1;
}
function getPageSize() {
if (!(grid)) {
return 10;
}
return grid.pager.pageSize();
}
function getFilters() {
if (!(grid)) {
return "";
}
return grid.dataSource.filter();
}
function getSorts() {
if (!(grid)) {
return "";
}
var arr = grid.dataSource.sort();
if ((arr) && (arr.length == 0)) {
return "";
}
var returnValue = "";
for (var index in arr) {
var type = "";
for (var col in grid.columns) {
if (grid.columns[col].field === arr[index].field) {
type = grid.columns[col].type;
}
}
returnValue += ((returnValue.length > 0) ? (";") : ("")) + arr[index].field + "," + (arr[index].dir === "asc") + "," + type;
}
return returnValue;
}
function getColumns() {
if (!(grid)) {
return "";
}
var columns = "";
for (var col in grid.columns) {
if (columns.length > 0) {
columns += ";";
}
columns += grid.columns[col].field + "," + grid.columns[col].type;
}
return columns;
}
var initGrid = true;
var grid2Data;
function getDataSource() {
$.ajax({
type: 'POST',
url: 'mydsurl' + getParams(),
data: "filter=" + JSON.stringify(getFilters()) + "&columns=" + getColumns(),
success: function (param) { grid2Data = param; },
//dataType: dataType,
async: false
});
return grid2Data.Data;
}
var shouldClickOnRefresh = false;
function refreshGrid() {
shouldClickOnRefresh = false;
$.ajax({
type: 'POST',
url: 'mydsurl' + getParams(),
data: "filter=" + JSON.stringify(getFilters()) + "&columns=" + getColumns(),
success: function (param) { grid2Data = param; },
//dataType: dataType,
async: false
});
grid.dataSource.total = function () {
return grid2Data.Total;
}
for (var col in grid.columns) {
if ((grid.columns[col].type) && (grid.columns[col].type === "Date")) {
for (var row in grid2Data.Data) {
grid2Data.Data[row][grid.columns[col].field] = new Date(parseInt((grid2Data.Data[row][grid.columns[col].field] + "").replace("/Date(", "").replace(")/", "")));
}
}
}
grid.dataSource.data(grid2Data.Data);
shouldClickOnRefresh = true;
}
function getParams() {
return getPageSize() + "|" + getPageIndex() + "|" + getSorts();
}
function bindGrid() {
var editUrl = 'myediturl';
if (!(editWindow)) {
editWindow = $("#edit-window");
}
$(".k-button.k-button-icontext.k-grid-edit").each(function (index) {
$(this).click(function () {
if (!editWindow.data("kendoWindow")) {
editWindow.kendoWindow({
title: "Edit User",
width: "60%",
height: "60%",
close: onClose,
open: onEditOpen,
content: editUrl + $("#grid").data().kendoGrid.dataSource.view()[index]["ID"]
});
}
else {
editWindow.data("kendoWindow").refresh(editUrl + $("#grid").data().kendoGrid.dataSource.view()[index]["ID"]);
editWindow.data("kendoWindow").open();
}
editWindow.data("kendoWindow").center();
return false;
})
});
$(".k-button.k-button-icontext.k-grid-delete").each(function (index) {
$(this).click(function () {
var r = confirm("Are you sure you want to delete this user?");
if (r == true) {
$.ajax({
type: 'POST',
url: 'mydelurl' + $("#grid").data().kendoGrid.dataSource.view()[index]["ID"],
success: function (param) { refreshGrid(); },
async: false
});
}
return false;
});
});
}
function onDataBound() {
if (!(shouldClickOnRefresh)) {
shouldClickOnRefresh = true;
bindGrid();
}
else {
refreshGrid();
}
}
$(function () {
$("#grid").kendoGrid({
dataBound: onDataBound,
dataSource: {
autoSync: true,
data: getDataSource(),
serverPaging: true,
schema: {
model: {
fields: {
Email: { type: "string" },
FullName: { type: "string" },
LogCreateDate: { type: "date" },
RoleName: { type: "string" },
UserName: { type: "string" }
}
},
total: function (response) {
return grid2Data.Total;
}
},
pageSize: 10
},
toolbar: ["create"],
scrollable: true,
sortable: true,
filterable: true,
pageable: {
input: true,
numeric: false,
pageSizes: true
},
columns: [
{
command: ["edit", "destroy"],
title: " "
},
{
field: "Email",
title: "Email",
type: "String"
},
{
field: "FullName",
title: "Full Name",
type: "String"
},
{
field: "LogCreateDate",
title: "Created",
type: "Date",
template: '#= kendo.toString(LogCreateDate,"MM/dd/yyyy") #'
},
{
field: "RoleName",
title: "Role",
type: "Custom"
},
{
field: "UserName",
type: "String"
}
],
editable: "popup"
});
grid = $("#grid").data("kendoGrid");
function onAddOpen() {
}
addWindow = $("#add-window");
$(".k-button.k-button-icontext.k-grid-add").click(function () {
if (!addWindow.data("kendoWindow")) {
addWindow.kendoWindow({
title: "Add User",
width: "60%",
height: "60%",
close: onClose,
open: onAddOpen,
content: 'myaddurl'
});
}
else {
addWindow.data("kendoWindow").open();
}
addWindow.data("kendoWindow").center();
addWindow.data("kendoWindow").refresh();
return false;
});
});
function onClose() {
$("#refresh-btn").click();
}
function onEditOpen() {
//editWindow.data("kendoWdinow").center();
}
I've hacked Kendo UI for the second time, this time I've solved its incompatibility with jQuery 1.8.3. using the following hack:
$(".k-grid-filter").each(function(index) {
$(this).click(function() {
$($(".k-filter-menu.k-popup.k-group.k-reset")[index]).offset({
left: $($(".k-grid-filter")[index]).offset().left - $($(".k-filter-menu.k-popup.k-group.k-reset")[index]).width(),
top: $($(".k-grid-filter")[index]).offset().top + $($(".k-grid-filter")[index]).height()})
})
});
I've put this hack into the document load event of the page an voila, it works. It surely looks ugly as hell with this hack, but after designing it will look good as new. I'm happy I found a work around, but I'm unhappy I had to hack Kendo UI twice. It is a very nice tool except the bugs.
jQuery 1.8.# is only compatible with Kendo UI - Q2 2012 SP1 (2012.2 913) or later..
If your kendo UI version is earlier, you should update it.

Resources