js oop style working in chrome but not in firefox - javascript

I have this script and it runs fine in chrome (and in JS online validators) but firefox throws me this error:
screen.initialize is not a function
Is this syntax somehow not in accordance with standards?
$(document).ready(function() {
screen = new Screen('t543f3r','user1','screen4',5);
screen.initialize();
}
Here is the Screen class:
//Our screen object
function Screen(hashKey,username,screenName,layout) {
this.hashKey = hashKey;
this.username = username;
this.screenName = screenName;
this.layout = layout;
//this.cacheRefreshInterval = 1000*60*5;
this.checkLayoutInterval = 1000*60*5; //check for new cache every 5 minutes
}
Screen.prototype.initialize = function() {
var self = this;
console.log('initializing screen '+this.screenName+' (layout is ['+this.layout+']) on player '+this.username+' using key '+this.hashKey);
var time = self.checkLayoutInterval;
setTimeout(function(){self.getValidLayout();}, time);
console.log('getValidLayout() set for '+time);
}
Screen.prototype.getValidLayout = function() {
var self = this;
var url = self.findBaseUrl(true) + 'getValidLayout/'+self.hashKey;
jQuery.ajax({
async: true,
url: url,
success: function(result) {
console.log('successfully fetched the valid screen layout: ['+result+']');
if (result != self.layout) {
window.location.reload();
}
},
error: function(result) {
console.log('there was an error fetching the screen layout');
},
complete: function() {
//setup next check
var time = self.checkLayoutInterval;
setTimeout(function(){self.getValidLayout();}, time);
console.log('next getValidLayout() set for '+time);
}
});
}

window.screen is a readonly property. When you assign to it (because you didn't declare your screen as a var), the assignment gets ignored.

Related

'No Data' view getting opens first and then Detail Page opens with the data in Fiori

I am developing a Master Detail application in which if the service URL doesn't return data, then a view called 'NoData' should open. But what actually is happening that first, the 'NoData' view opens and then the Detail Page with the data gets displayed. I don't know why and how that 'NoData' page is appearing first. Below is my code for Master Page :
Controller.js :
onInit: function () {
this.router = sap.ui.core.UIComponent.getRouterFor(this);
this._custTemp = this.getView().byId("listItemTemp").clone();
this.refreshFlag = true; // Flag to get new data or not for customers
this.totalModel = sap.ui.getCore().getModel("totalModel");
this.getView().setModel(this.totalModel, "totalModel");
this.oDataModel = sap.ui.getCore().getModel("DataModel");
this.getView().setModel(this.oDataModel, "DataModel");
this.oInitialLoadFinishedDeferred = jQuery.Deferred();
var oEventBus = sap.ui.getCore().getEventBus();
this.getView().byId("listId").attachEvent("updateFinished", function () {
this.oInitialLoadFinishedDeferred.resolve();
oEventBus.publish("MasterPage", "InitialLoadFinished", {
oListItem: this.getView().byId("listId").getItems()[0]
});
if (!sap.ui.Device.system.phone) {
this._getFirstItem();
}
}, this);
this.functionData = [];
},
waitForInitialListLoading: function (fnToExecute) {
jQuery.when(this.oInitialLoadFinishedDeferred).then(jQuery.proxy(fnToExecute, this));
},
_getFirstItem: function () {
sap.ui.core.BusyIndicator.show();
this.waitForInitialListLoading(function () {
// On the empty hash select the first item
var list = this.getView().byId("listId");
var selectedItem = list.getItems()[0];
if (selectedItem) {
list.setSelectedItem(selectedItem, true);
var data = list.getBinding("items").getContexts()[0];
sap.ui.getCore().getModel("detailModel").setData(data.getObject());
this.router.navTo('DetailPage', {
QueryNo: data.EICNO
});
sap.ui.core.BusyIndicator.hide();
} else {
this.router.navTo('NoData');
}
}, this);
},
onBeforeRendering: function () {
this._fnGetData();
},
_fnGetData: function (oEvent) {
var that = this;
this.getView().setModel(this.totalModel, "totalModel");
if (this.refreshFlag === true) {
sap.ui.core.BusyIndicator.show(0);
$.ajax({
url: "/sap/opu/odata/sap/ZHR_V_CARE_SRV/EmpQueryInitSet('10002001')?$expand=QueryLoginToQueryList/QueryToLog",
method: "GET",
dataType: "json",
success: function (data) {
that.getView().getModel("totalModel").setData(data.d.QueryLoginToQueryList);
that.refreshFlag = false;
sap.ui.core.BusyIndicator.hide();
that.statusList();
},
error: function (err) {
sap.ui.core.BusyIndicator.hide();
MessageBox.information(err.responseText + "Please try again");
}
});
}
}
totalModel is a json model, right? You'll get two updateFinished events on app load. The first one is triggered once the list control is rendered and binding is done (when the model has no data), and the second comes after your $.ajax call updates data to totalModel.
I think you can solve it by moving your NoData navigation to both 'success' and 'error' callbacks of your $.ajax call. Doing so may cover other use cases e.g. if you are using URL navigation parameters and a user changes the entity ID in the URL to some random number, it'd navigate to your NoDatapage.

knockout bind do not updating when observable has changed

I've a dropdown button which should be avaiable after ajax request will be finished.
<div class="form-input">
<label class="">Sort by:</label>
<select name="orderby" class="selectpicker" data-bind="value: sortBy, optionsCaption: 'Default', disable: waiting">
<option value="some_value">some_option</option>
<option value="some_value">some_option</option>
</select>
</div>
On page requested, it initially load data
$(function() {
//Initialization
var vm = new ArticleViewModel();
initialLoadArticles(vm);
ko.applyBindings(vm, $("#article-plugin")[0]);
});
function ArticleViewModel() {
var self = this;
//options =>
this.articles = ko.observableArray([]);
this.pageSize = 12;
this.sortBy = ko.observable('asc');
this.currentPage = ko.observable(1);
this.waiting = ko.observable(true);
this.totalPages = 0;
this.initMode = true;
this.timerId = null;
this.viewTemplate = ko.observable('listview-template');
if (this.viewTemplate() === "listview-template") {
this.pageSize = 4
} else {
this.pageSize = 12
};
this.sortBy.subscribe(function(event) {
console.log(event);
self.optionChanged();
loadArticles(self);
});
this.optionChanged = function() {
this.currentPage(1);
}
this.setCardView = function() {
self.viewTemplate('cardview-template');
loadArticles(self);
}
this.setListView = function() {
self.viewTemplate('listview-template');
loadArticles(self);
}
}
function initialLoadArticles(vm) {
vm.waiting(true);
var params = {
page: vm.currentPage(),
size: vm.pageSize,
sortby: vm.sortBy()
};
api.ajax.get(api.urls.article.getArticles, params, function(response) {
console.log('waiting: ' + vm.waiting());
if (response.success) {
vm.articles(response.data.items);
vm.waiting(false);
}
});
}
Well, on a page it display all articles, but dropdown button still blocked and I don't what exactly could be the problem of that.
I'd suggest a few changes to your viewmodel, featuring automatic loading via a subscription.
I think you always want to set waiting to false after loading, independent of whether the request was a success or not. Also think about low-level request errors, you need to add a handler for those.
function ArticleViewModel() {
var self = this;
self.articles = ko.observableArray();
self.pageSize = ko.observable();
self.sortBy = ko.observable('asc');
self.currentPage = ko.observable();
self.waiting = ko.observable(true);
self.viewTemplate = ko.observable();
// API
self.setCardView = function() {
self.viewTemplate('cardview-template');
self.pageSize(12);
self.currentPage(1);
};
self.setListView = function() {
self.viewTemplate('listview-template');
self.pageSize(4);
self.currentPage(1);
};
// compute Ajax-relevant parameters
self.ajaxParams = ko.pureComputed(function () {
return {
page: self.currentPage(),
size: self.pageSize(),
sortby: self.sortBy()
};
}).extend({ rateLimit: { timeout: 10, method: 'notifyWhenChangesStop' } });
// auto-load when params change
self.ajaxParams.subscribe(function (params) {
self.waiting(true);
api.ajax.get(api.urls.article.getArticles, params, function (response) {
if (response.success) {
self.articles(response.data.items);
}
self.waiting(false);
});
});
// set inital view (also triggers load)
self.setListView();
}
$(function() {
var vm = new ArticleViewModel();
ko.applyBindings(vm, $('#article-plugin')[0]);
});
More strictly speaking, you I'd advice against true or false as the "loading" indicator. It's technically possible that more than one Ajax request is running and this would be a race condition. The first request that comes back resets the "loading" state, and the next one still overwrites the viewmodel data. Either use a counter, or prevent new requests while there is a pending one.
The rateLimit extender makes sure that a rapid succession of changes to the parameters, like what happens when setListView() is called, does not cause multiple Ajax requests.
If your Ajax requests are done by jQuery internally, I would suggest the following setup to be able to make use of the done, fail and always promise handlers:
function ApiWrapper() {
var self = this;
function unwrapApiResponse(response) {
if (response.success) {
return new $.Deferred().resolve(response.data).promise();
} else {
return new $.Deferred().reject(response.error).promise();
}
}
self.getArticles = function (params) {
return $.get('articleUrl', params).then(unwrapApiResponse);
};
// more functions like this
}
var api = new ApiWrapper();
and in your viewmodel:
self.ajaxParams.subscribe(function (params) {
self.waiting(true);
api.getArticles(params).done(function (data) {
self.articles(data.items);
}).fail(function (err) {
// show error
}).always(function () {
self.waiting(false);
});
});

Jquery how clean formData object in my own upload plugin

I tried to create my own plugin for upload files via ajax.
If the page where there is the input file is reloaded after upload It seems to work good.
If the page where there is the input file is NOT reloaded after upload (because was reloaded only ajax content) There are problems with IE and Chrome because the files to upload are appended to previous just uploaded (with firefox is ok).
I tried to fix it by cleaning the input file after the first upload but in this way then with IE and Chrome I can no longer upload other files.
MY FIX
complete: function () {
defaults.onFinish.call(this);
// If page where is the input file not reloaded
// after upload files IE and Chrome not working
$this.replaceWith($this.val('').clone(true));
$this.val('');
}
In truth I would clean the formData object after every upload but I haven't been able to do it
MY PLUGIN
;(function ($, window, document, undefined) {
// Function-level strict mode syntax
'use strict';
$.fn.ajaxUpload = function(options) {
var defaults = {
num_files : 0,
max_files : 2,
max_concurrent : 10,
max_filesize : 1024 * 4096,
php_max_size : 1024 * 8192,
allowed_types : ['jpeg','jpg'],
ajax_url : 'action.php',
var_name : 'file',
extra_fields : {},
onFinish : function() {}
};
var options = $.extend(defaults, options);
return this.each(function() {
var $this = $(this);
$this.on('change', function() {
var files = $this[0].files;
var len = files.length;
var items = 0;
var diff_files = parseInt(defaults.max_files - defaults.num_files - len);
if(diff_files < 0) {
return false;
}
if(!maxUploadFiles(len, defaults.max_concurrent)) {
return false;
}
var formdata = new FormData();
jQuery.each(files, function(i, file) {
if(!isOverSized(file, defaults.max_filesize)) {
return false;
}
if(!isAllowedTypes(file, defaults.allowed_types)) {
return false;
}
if(!totalFilesSize(file, defaults.php_max_size)) {
return false;
}
formdata.append(defaults.var_name + '['+i+']', file);
items++;
});
// Append extra data to formdata
$.each(defaults.extra_fields, function(name, value) {
formdata.append(name, value);
});
// Check that files have passed all test
if (len != items) { return false; }
$.ajax({
url: defaults.ajax_url,
data: formdata,
cache: false,
contentType: false,
processData: false,
type: 'POST',
beforeSend: function () {
},
success: function(data) {
totalSize = 0;
},
complete: function () {
defaults.onFinish.call(this);
// If page where is the input file not reloaded
// after upload files IE and Chrome not working
//$this.replaceWith($this.val('').clone(true));
//$this.val('');
}
});
});
});
};
var totalSize = 0;
function totalFilesSize(file, php_max_size) {
totalSize += file.size;
if(totalSize > php_max_size) {
totalSize = 0;
return false;
}
return true;
}
function maxUploadFiles(len, max_concurrent) {
if(len > max_concurrent) {
return false;
}
return true;
}
function isAllowedTypes(file, allowed_types) {
var ext = file.name.split('.').pop().toLowerCase();
if(jQuery.inArray(ext, allowed_types) < 0) {
return false;
}
return true;
}
function isOverSized(file, max_filesize) {
if(file.size > max_filesize) {
return false;
}
return true;
}
})(jQuery, window, document);
According to you that changes should I do to solve my problem?
Thank you
EDIT
I add this line on complete, and It seems to work
$this.val('');
$this.wrap('<form>').parent('form').trigger('reset');
$this.unwrap();
$this.replaceWith($this.clone());
The problem with your plugin is that you keep a reference to the original input with $this and then tried to replace it with a clone. Because you are cloning is better to get a new reference each time so you should unbind and bind .
(function ($, window, document, undefined) {
// Function-level strict mode syntax
'use strict';
$.fn.ajaxUpload = function (options) {
var defaults = {
num_files: 0,
max_files: 2,
max_concurrent: 10,
max_filesize: 1024 * 4096,
php_max_size: 1024 * 8192,
allowed_types: ['jpeg', 'jpg'],
ajax_url: 'action.php',
var_name: 'file',
extra_fields: {},
onFinish: function () {}
};
var options = $.extend(defaults, options);
var bindInput = function (elem) {
var element = $(elem),
bindFunc = function (evt) {
var files = evt.currentTarget.files;
var len = files.length;
var items = 0;
var diff_files = parseInt(defaults.max_files - defaults.num_files - len);
if (diff_files < 0) {
return false;
}
if (!maxUploadFiles(len, defaults.max_concurrent)) {
return false;
}
var formdata = new FormData();
jQuery.each(files, function (i, file) {
if (!isOverSized(file, defaults.max_filesize)) {
return false;
}
if (!isAllowedTypes(file, defaults.allowed_types)) {
return false;
}
if (!totalFilesSize(file, defaults.php_max_size)) {
return false;
}
formdata.append(defaults.var_name + '[' + i + ']', file);
items++;
});
// Append extra data to formdata
$.each(defaults.extra_fields, function (name, value) {
formdata.append(name, value);
});
// Check that files have passed all test
if (len != items) {
return false;
}
$.ajax({
url: defaults.ajax_url,
data: formdata,
cache: false,
contentType: false,
processData: false,
type: 'POST',
beforeSend: function () {},
success: function (data) {
totalSize = 0;
},
complete: function () {
defaults.onFinish.call(this);
var previous = $(evt.currentTarget);
previous.off('change', bindFunc);
var newElem = previous.val('').clone(true)
previous.replaceWith(newElem);
bindInput(newElem);
}
});
};
element.on('change', bindFunc);
};
return this.each(function () {
bindInput(this)
});
};
var totalSize = 0;
function totalFilesSize(file, php_max_size) {
totalSize += file.size;
if (totalSize > php_max_size) {
totalSize = 0;
return false;
}
return true;
}
function maxUploadFiles(len, max_concurrent) {
if (len > max_concurrent) {
return false;
}
return true;
}
function isAllowedTypes(file, allowed_types) {
var ext = file.name.split('.').pop().toLowerCase();
if (jQuery.inArray(ext, allowed_types) < 0) {
return false;
}
return true;
}
function isOverSized(file, max_filesize) {
if (file.size > max_filesize) {
return false;
}
return true;
}
})(jQuery, window, document);
{Edit}
The problem that originate your question is the nightmare of every file upload plugin developer. As you are developing a plugin you should be aware that the input tag may contain other styles and event handlers set by the consumer of the plugin that you must preserve or you will break existing functionality.
For security reasons the value of the input type file cannot be changed with javascript. There are a lot of answers in SO about that. Search for clear+input+file and see for yourself, the most remarkable is this Clearing <input type='file' /> using jQuery
As you can see there are basically two choices:
Clone the input and call val('') before cloning (calling jQuery $(input).val('') is not the same that calling input.value = '').
The problems of this approach is for example that in IE this event is called twice when clearing the file input and you must be carefull about releasing memory and references to the input being replaced while preserving current styles and event handlers that were not set by your plugin
The second is better but has issues as well. Wrap your input in a form tag and call the form's reset method.
input.wrap('<form>').parent('form').trigger('reset');
input.unwrap();
Check the docs about the sintax of the form tag and you will see the following quote
Note: It's strictly forbidden to nest a form inside another form. Doing so can behave in an unpredictable way that will depend on which browser the user is using.
The main reasoning behind that is that your plugin can be applied to an input tag that is already inside a form leaving you with invalid html so you must wrap the form call the reset method and remove this form right away. Also remember that forms may have visual styles applied to them breaking the user interface if you leave them around.
In the second alternative is easier to fix your code. Just change the complete callback like this. No cloning is needed in this case.
complete: function () {
defaults.onFinish.call(this);
$this.wrap('<form>').parent('form').trigger('reset');
$this.unwrap();
}
This changes should happen so fast that the users will not notice them. I tested with 1000 elements around and no visual glitches were visible.

JavaScript timer is not working

I have created a file named ExtremeNotifications.js and added to the _Layout.cshtml master layout.
The ExtremeNotifications.js includes the following JavaScript code:
var extremeNotifications = extremeNotifications || {};
extremeNotifications = (function () {
var baseURL = document.baseURI;
var url = baseURL + 'api/usernotifications';
var isNotificationServiceStarted = false;
var timer;
function getPendingNotifications() {
$.ajax({ url: url, success: dataRetrieved, type: 'GET', dataType: 'json' });
}
function dataRetrieved(data) {
alert(data);
}
function startNotifications() {
if (!isNotificationServiceStarted) {
timer = setInterval(getPendingNotifications, 3000);
isNotificationServiceStarted = true;
}
}
function stopNotifications() {
clearInterval(timer);
isNotificationServiceStarted = false;
}
return {
start: startNotifications(),
getPendingNotifications: getPendingNotifications(),
isNotificationServiceStarted: isNotificationServiceStarted,
stop: stopNotifications()
}
})();
Then in my Home Index.cshtml I start the notifications with the following code and only if User.Identity.IsAuthenticated:
<script>
extremeNotifications.start();
</script>
So now when my page starts and I'm authenticated user I get an alert box in the first time but I never see another alert after 3 seconds.
Any comments?
You're close, but you're creating that returned object incorrectly:
return {
start: startNotifications,
getPendingNotifications: getPendingNotifications,
isNotificationServiceStarted: isNotificationServiceStarted,
stop: stopNotifications
};
By including the () after the function names, your code was calling the functions and returning their return values instead of returning references to the functions themselves.

How to cancel asynchronous process in javascript?

I have a one-window javascript application. I have a dashboard that displays certain images by loading via multiple get requests in the background.
Problem arises when not all get requests are finished on time and the context of the site changes because then I want to clear the dashboard. Yet if the get request havent't finished yet, they will populate the dashboard with the wrong images.
I am trying to think of a way to abort those get request. Can someone please direct me in the right direction?
var Dashboard = {
showAllAssets: function(){
var self = this;
this.resetDashboard();
$.get(this.urlForAllAssets, function(json){
self.loadAssets(json);
});
},
showAssetsForCategory: function(categoryId) {
...
},
getHtmlForAsset: function(id) {
var self = this;
$.get(this.urlForDashboardThumb + "/" + id.toString(), function(assetHtml){
var $asset = $(assetHtml);
self.insertAssetThumbIntoDom($asset);
// this gets inserted even when context changed, how can I prevent that?
var thumb = Object.create(Thumbnail);
thumb.init($asset);
}, 'html')
},
insertAssetThumbIntoDom: function($asset) {
$asset.appendTo(this.$el);
},
resetDashboard: function() {
this.$el.html("");
},
loadAssets: function(idList) {
var self = this;
var time = 200;
// These get requests will pile up in the background
$.each(idList, function(){
var asset = this;
setTimeout(function(){
self.getHtmlForAsset(asset.id);
}, time);
time += 200;
});
},
bind: function() {
$document.on('loadAssets', function(event, idList) {
self.loadAssets(idList);
});
$document.on('switched_to_category', function(event, categoryId) {
self.showAssetsForCategory(categoryId);
});
$document.on('show_all_assets', function(){
self.showAllAssets();
})
},
init: function($el) {
this.$el = $el;
this.resetDashboard();
this.bind();
}
}
Though you cant stop an already sent request, you can still solve your problem.
My solution is to generate a simple ID, a random set of numbers for example, and store somewhere in your dashboard, and send it along with the request and send it back with the image.
If a new context is generated, it will have a new ID.
If the image comes back with a different ID than the one in the current context, then discard it.
As pointed out by the comments, a possible solution is to store the current context and compare it within the success method on the get request.
I have changed my code insofar that now I'll store the current within the manager and also I pass the event around to the $.get-method.
This has the downside that the get requests are still processed though and the loading of the new context takes longer as those get requests are processed later if there are too many to process. I also dislike passing the event around.
var Dashboard = {
currentLoadEvent: null,
loadAssets: function(idList, event) {
var self = this;
$.each(idList, function(){
var asset = this;
self.getHtmlForAsset(asset.id, event);
});
},
getHtmlForAsset: function(id, event) {
var self = this;
$.get(this.urlForDashboardThumb + "/" + id.toString(), function(assetHtml){
if (event === self.currentLoadEvent) {
console.log('same event continuing');
var $asset = $(assetHtml);
self.insertAssetThumbIntoDom($asset);
var thumb = Object.create(Thumbnail);
thumb.init($asset);
} else {
console.log('context changed');
}
}, 'html')
},
bind: function() {
var self = this;
$document.on('loadAssets', function(event, idList) {
self.currentLoadEvent = event;
self.loadAssets(idList, event);
});
$document.on('switched_to_category', function(event, categoryId) {
self.currentLoadEvent = event;
self.showAssetsForCategory(categoryId, event);
});
$document.on('show_all_assets', function(event){
self.currentLoadEvent = event;
self.showAllAssets(event);
})
}
}
I created a different solution by storing the request in an array and aborting them when the context changed:
loadAssets: function(idList, event) {
var self = this;
var requests = [];
$.each(idList, function(){
var asset = this;
if (self.currentLoadEvent === event){
var request = $.get(self.urlForDashboardThumb + "/" + asset.id.toString(), function(assetHtml){
if (event === self.currentLoadEvent) {
var $asset = $(assetHtml);
self.insertAssetThumbIntoDom($asset);
var thumb = Object.create(Thumbnail);
thumb.init($asset);
console.log('completed get request');
} else {
console.log('context changed');
$.each(requests, function(){
this.abort();
console.log('aborted request');
})
}
}, 'html');
requests.push(request);
} else {
return false;
}
});
},

Categories

Resources