I'd like some insight into a little problem I'm encountering with a custom JS plugin that I've made. It's reasonably simple, I'm using a plugin JS template to write out plugins, which I'll attach. I'm then initialising the plugin, and ideally need to figure out if there's a way I can check if the plugin JS file is loaded before doing anything:
plugin template
(function() {
this.MyPlugin = function() {
// default settings
const INBOUND_CONFIG = {
isEnabled: false
}
// Create options by extending defaults with the passed in arugments
if (arguments[0] && typeof arguments[0] === "object") {
this.options = extendDefaults(INBOUND_CONFIG, arguments[0]);
}
// custom public method
HoneycombInbound.prototype.getSettings = function() {
}
// Utility method to extend defaults with user options
function extendDefaults(source, properties) {
var property;
for (property in properties) {
if (properties.hasOwnProperty(property)) {
source[property] = properties[property];
}
}
return source;
}
}
}());
With the above, I'd initialise the plugin in HTML as follows:
<script src="./my-plugin.js"></script>
<script>
const plugin = new MyPlugin()
// this doesn't work if the script file isn't linked:
if (plugin) {
// do something
}
</script>
Unfortunately, a simple check of plugin doesn't actually work if the JS file isn't loaded for instance, I've even tried something like: if (new MyPlugin()) {} with little hope.
I essentially need a way of not throwing an error in the browser. Any solution I can use to check correctly if it's loaded before executing?
A solution that has worked for me is to work with the WebAPI postMessage
I've used to communicate with the client, that the plugin is ready.
So on the plugin you will need something like this:
window.postMessage("The plugin is ready to go" );
And on your client you will need to add a new listener that can catch the message:
window.addEventListener("message", myMessage, false);
const myMessage = (event) => {
if (event.data === "The plugin is ready to go")
// This is your message so do your stuff here
...
}
EDIT
So to achieve this behavio, you will need to enable on the client sidethe initialization:
const myPlugin = {
init: () => {
// Initilize the plugin if you are using the DOM you can add a parameter with an id to mount it whenever you need it
//Otherwise just configure or initilize the variables that you will use
// And here you need to pass the messsage to tell the client that everyting is ready
// Here you need to configure the origin correctly depending on your needs check the documentation for more details
window.addEventListener("message", myMessage, false);
},
someFunctionality: () => {
//This pattern is to expose the funtionalities to the client so you can achieve any
}
};
So now on your client you just need to add your script and tell the plugin to initialize:
<script type='text/javascript' src='/myPlugin.js'></script>
<script>
window.addEventListener("message", myMessage, false);
const myMessage = (event) => {
if (event.data === "The plugin is ready to go")
// This is your message so do your stuff here
...
}
myPlugin.init();
</script>
I am using this code to infinite load a page on squarespace. My problem is the reloading doesn't capture the filtering that I have set up in my url. It cannot seem to 'see' the variables or even the url or categoryFilter in my collection. I've tried to use a .var directive but the lazy loaded items cannot see the scope of things defined before it. I'm running out of ideas here please help!
edit: I've since found the answer but gained another question.
I was able to use window.location.href instead of window.location.pathname to eventually get the parameters that way. Except this doesn't work in IE11 so now I have to search for this.
<script>
function infiniteScroll(parent, post) {
// Set some variables. We'll use all these later.
var postIndex = 1,
execute = true,
stuffBottom = Y.one(parent).get('clientHeight') + Y.one(parent).getY(),
urlQuery = window.location.pathname,
postNumber = Static.SQUARESPACE_CONTEXT.collection.itemCount,
presentNumber = Y.all(post).size();
Y.on('scroll', function() {
if (presentNumber >= postNumber && execute === true) {
Y.one(parent).append('<h1>There are no more posts.</h1>')
execute = false;
} else {
// A few more variables.
var spaceHeight = document.documentElement.clientHeight + window.scrollY,
next = false;
/*
This if statement measures if the distance from
the top of the page to the bottom of the content
is less than the scrollY position. If it is,
it's sets next to true.
*/
if (stuffBottom < spaceHeight && execute === true) {
next = true;
}
if (next === true) {
/*
Immediately set execute back to false.
This prevents the scroll listener from
firing too often.
*/
execute = false;
// Increment the post index.
postIndex++;
// Make the Ajax request.
Y.io(urlQuery + '?page=' + postIndex, {
on: {
success: function (x, o) {
try {
d = Y.DOM.create(o.responseText);
} catch (e) {
console.log("JSON Parse failed!");
return;
}
// Append the contents of the next page to this page.
Y.one(parent).append(Y.Selector.query(parent, d, true).innerHTML);
// Reset some variables.
stuffBottom = Y.one(parent).get('clientHeight') + Y.one(parent).getY();
presentNumber = Y.all(post).size();
execute = true;
}
}
});
}
}
});
}
// Call the function on domready.
Y.use('node', function() {
Y.on('domready', function() {
infiniteScroll('#content','.lazy-post');
});
});
</script>
I was able to get this script working the way I wanted.
I thought I could use:
Static.SQUARESPACE_CONTEXT.collection.itemCount
to get {collection.categoryFilter} like with jsont, like this:
Static.SQUARESPACE_CONTEXT.collection.categoryFilter
or this:
Static.SQUARESPACE_CONTEXT.categoryFilter
It didn't work so I instead changed
urlQuery = window.location.pathname
to
urlQuery = window.location.href
which gave me the parameters I needed.
The IE11 problem I encountered was this script uses
window.scrollY
I changed it to the ie11 compatible
Window.pageYOffset
and we were good to go!
Writing the code i am stuck with one thing. I am loading variable string through jQuery load function and there is where trouble starts. I want my code to check if string loaded through text file had any changes and if that is true make some actions. How do I set my variable as a string to compare it with the one in file? Part of the code
var follow, donate;
var auto_refresh = setInterval(function() {
follow = $('#followerid').load("../Muxy/most_recent_follower.txt");
donate = $('#donatorid').load("../Muxy/most_recent_donator.txt");
}, 100);
and then I want to make something like this:
someUpdateFunction() {
if($(#'followerid').get("innerHTML") != follow)
// actions (animations, div changes etc.)
}
That is probably totally wrong but I wasnt able to find any tips here. Thanks in advance
Try something like this:
$.get('../Muxy/most_recent_follower.txt', function (data) {
var followerid = $('#followerid').html();
if( followerid == data ){
// They are the same
}else{
// They are not the same
}
});
The .html() method will get the HTML inside #followerid. It sounds like that's what you want.
The jQuery.load function doesn't return the loaded file, it returns the element. Try out this:
var auto_refresh = setInterval(function() {
oldFollow = $('#followerid').html();
$('#followerid').load("../Muxy/most_recent_follower.txt");
oldDonate = $('#donatorid').html();
$('#donatorid').load("../Muxy/most_recent_donator.txt");
}, 100);
someUpdateFunction() {
if($('#followerid').html() !== oldFollow)
// actions (animations, div changes etc.)
}
}
I have JQuery Mobile-1.0.js file.
// Load a page into the DOM.
$.mobile.loadPage = function (url, options) {
// This function uses deferred notifications to let callers
// know when the page is done loading, or if an error has occurred.
var deferred = $.Deferred(),
// The default loadPage options with overrides specified by
// the caller.
settings = $.extend({}, $.mobile.loadPage.defaults, options),
// The DOM element for the page after it has been loaded.
page = null,
// If the reloadPage option is true, and the page is already
// in the DOM, dupCachedPage will be set to the page element
// so that it can be removed after the new version of the
// page is loaded off the network.
dupCachedPage = null,
// determine the current base url
findBaseWithDefault = function () {
var closestBase = ($.mobile.activePage && getClosestBaseUrl($.mobile.activePage));
return closestBase || documentBase.hrefNoHash;
},
// The absolute version of the URL passed into the function. This
// version of the URL may contain dialog/subpage params in it.
absUrl = path.makeUrlAbsolute(url, findBaseWithDefault());
// If the caller provided data, and we're using "get" request,
// append the data to the URL.
if (settings.data && settings.type === "get") {
absUrl = path.addSearchParams(absUrl, settings.data);
settings.data = undefined;
}
// If the caller is using a "post" request, reloadPage must be true
if (settings.data && settings.type === "post") {
settings.reloadPage = true;
}
// The absolute version of the URL minus any dialog/subpage params.
// In otherwords the real URL of the page to be loaded.
var fileUrl = path.getFilePath(absUrl),
// The version of the Url actually stored in the data-url attribute of
// the page. For embedded pages, it is just the id of the page. For pages
// within the same domain as the document base, it is the site relative
// path. For cross-domain pages (Phone Gap only) the entire absolute Url
// used to load the page.
dataUrl = path.convertUrlToDataUrl(absUrl);
// Make sure we have a pageContainer to work with.
settings.pageContainer = settings.pageContainer || $.mobile.pageContainer;
// Check to see if the page already exists in the DOM.
page = settings.pageContainer.children(":jqmData(url='" + dataUrl + "')");
// If we failed to find the page, check to see if the url is a
// reference to an embedded page. If so, it may have been dynamically
// injected by a developer, in which case it would be lacking a data-url
// attribute and in need of enhancement.
if (page.length === 0 && dataUrl && !path.isPath(dataUrl)) {
page = settings.pageContainer.children("#" + dataUrl)
.attr("data-" + $.mobile.ns + "url", dataUrl);
}
// If we failed to find a page in the DOM, check the URL to see if it
// refers to the first page in the application. If it isn't a reference
// to the first page and refers to non-existent embedded page, error out.
if (page.length === 0) {
if ($.mobile.firstPage && path.isFirstPageUrl(fileUrl)) {
// Check to make sure our cached-first-page is actually
// in the DOM. Some user deployed apps are pruning the first
// page from the DOM for various reasons, we check for this
// case here because we don't want a first-page with an id
// falling through to the non-existent embedded page error
// case. If the first-page is not in the DOM, then we let
// things fall through to the ajax loading code below so
// that it gets reloaded.
if ($.mobile.firstPage.parent().length) {
page = $($.mobile.firstPage);
}
} else if (path.isEmbeddedPage(fileUrl)) {
deferred.reject(absUrl, options);
return deferred.promise();
}
}
// Reset base to the default document base.
if (base) {
base.reset();
}
// If the page we are interested in is already in the DOM,
// and the caller did not indicate that we should force a
// reload of the file, we are done. Otherwise, track the
// existing page as a duplicated.
if (page.length) {
if (!settings.reloadPage) {
enhancePage(page, settings.role);
deferred.resolve(absUrl, options, page);
return deferred.promise();
}
dupCachedPage = page;
}
var mpc = settings.pageContainer,
pblEvent = new $.Event("pagebeforeload"),
triggerData = { url: url, absUrl: absUrl, dataUrl: dataUrl, deferred: deferred, options: settings };
// Let listeners know we're about to load a page.
mpc.trigger(pblEvent, triggerData);
// If the default behavior is prevented, stop here!
if (pblEvent.isDefaultPrevented()) {
return deferred.promise();
}
if (settings.showLoadMsg) {
// This configurable timeout allows cached pages a brief delay to load without showing a message
var loadMsgDelay = setTimeout(function () {
$.mobile.showPageLoadingMsg();
}, settings.loadMsgDelay),
// Shared logic for clearing timeout and removing message.
hideMsg = function () {
// Stop message show timer
clearTimeout(loadMsgDelay);
// Hide loading message
$.mobile.hidePageLoadingMsg();
};
}
if (!($.mobile.allowCrossDomainPages || path.isSameDomain(documentUrl, absUrl))) {
deferred.reject(absUrl, options);
} else {
// Load the new page.
$.ajax({
url: fileUrl,
type: settings.type,
data: settings.data,
dataType: "html",
success: function (html, textStatus, xhr) {
//pre-parse html to check for a data-url,
//use it as the new fileUrl, base path, etc
var all = $("<div></div>"),
//page title regexp
newPageTitle = html.match(/<title[^>]*>([^<]*)/) && RegExp.$1,
// TODO handle dialogs again
pageElemRegex = new RegExp("(<[^>]+\\bdata-" + $.mobile.ns + "role=[\"']?page[\"']?[^>]*>)"),
dataUrlRegex = new RegExp("\\bdata-" + $.mobile.ns + "url=[\"']?([^\"'>]*)[\"']?");
// data-url must be provided for the base tag so resource requests can be directed to the
// correct url. loading into a temprorary element makes these requests immediately
if (pageElemRegex.test(html)
&& RegExp.$1
&& dataUrlRegex.test(RegExp.$1)
&& RegExp.$1) {
url = fileUrl = path.getFilePath(RegExp.$1);
}
if (base) {
base.set(fileUrl);
}
//workaround to allow scripts to execute when included in page divs
all.get(0).innerHTML = html;
page = all.find(":jqmData(role='page'), :jqmData(role='dialog')").first();
//if page elem couldn't be found, create one and insert the body element's contents
if (!page.length) {
page = $("<div data-" + $.mobile.ns + "role='page'>" + html.split(/<\/?body[^>]*>/gmi)[1] + "</div>");
}
if (newPageTitle && !page.jqmData("title")) {
if (~newPageTitle.indexOf("&")) {
newPageTitle = $("<div>" + newPageTitle + "</div>").text();
}
page.jqmData("title", newPageTitle);
}
//rewrite src and href attrs to use a base url
if (!$.support.dynamicBaseTag) {
var newPath = path.get(fileUrl);
page.find("[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]").each(function () {
var thisAttr = $(this).is('[href]') ? 'href' :
$(this).is('[src]') ? 'src' : 'action',
thisUrl = $(this).attr(thisAttr);
// XXX_jblas: We need to fix this so that it removes the document
// base URL, and then prepends with the new page URL.
//if full path exists and is same, chop it - helps IE out
thisUrl = thisUrl.replace(location.protocol + '//' + location.host + location.pathname, '');
if (!/^(\w+:|#|\/)/.test(thisUrl)) {
$(this).attr(thisAttr, newPath + thisUrl);
}
});
}
//append to page and enhance
// TODO taging a page with external to make sure that embedded pages aren't removed
// by the various page handling code is bad. Having page handling code in many
// places is bad. Solutions post 1.0
page
.attr("data-" + $.mobile.ns + "url", path.convertUrlToDataUrl(fileUrl))
.attr("data-" + $.mobile.ns + "external-page", true)
.appendTo(settings.pageContainer);
// wait for page creation to leverage options defined on widget
page.one('pagecreate', $.mobile._bindPageRemove);
enhancePage(page, settings.role);
// Enhancing the page may result in new dialogs/sub pages being inserted
// into the DOM. If the original absUrl refers to a sub-page, that is the
// real page we are interested in.
if (absUrl.indexOf("&" + $.mobile.subPageUrlKey) > -1) {
page = settings.pageContainer.children(":jqmData(url='" + dataUrl + "')");
}
//bind pageHide to removePage after it's hidden, if the page options specify to do so
// Remove loading message.
if (settings.showLoadMsg) {
hideMsg();
}
// Add the page reference and xhr to our triggerData.
triggerData.xhr = xhr;
triggerData.textStatus = textStatus;
triggerData.page = page;
// Let listeners know the page loaded successfully.
settings.pageContainer.trigger("pageload", triggerData);
deferred.resolve(absUrl, options, page, dupCachedPage);
},
error: function (xhr, textStatus, errorThrown) {
//set base back to current path
if (base) {
base.set(path.get());
}
// Add error info to our triggerData.
triggerData.xhr = xhr;
triggerData.textStatus = textStatus;
triggerData.errorThrown = errorThrown;
var plfEvent = new $.Event("pageloadfailed");
// Let listeners know the page load failed.
settings.pageContainer.trigger(plfEvent, triggerData);
// If the default behavior is prevented, stop here!
// Note that it is the responsibility of the listener/handler
// that called preventDefault(), to resolve/reject the
// deferred object within the triggerData.
if (plfEvent.isDefaultPrevented()) {
return;
}
// Remove loading message.
if (settings.showLoadMsg) {
// Remove loading message.
hideMsg();
//show error message
$("<div class='ui-loader ui-overlay-shadow ui-body-e ui-corner-all'><h1>" + $.mobile.pageLoadErrorMessage + "</h1></div>")
.css({ "display": "block", "opacity": 0.96, "top": $window.scrollTop() + 100 })
.appendTo(settings.pageContainer)
.delay(800)
.fadeOut(400, function () {
$(this).remove();
});
}
deferred.reject(absUrl, options);
}
});
}
return deferred.promise();
};
This is the code for showing an error message "Error Loading Page" for error in page. Here i want to show alert message for net connection failure as "Please check your net connection" instead of the below image.
Note: I dont want to change the pageloaderrormessage. want to stop to get the page error messages, instead of that i will enable my network error condition as in Show Network Error in android. If the user pressed "Ok" in alert dialog i'll navigate them into Reload.html.
Please tell me where i can check that condition and where i have to change the error message?
As both #shkschneider and #codemonkey have suggested you need to set this option on mobileinit
Example:
$(document).bind("mobileinit", function(){
$.mobile.pageLoadErrorMessage = "Please check your net connection";
});
Linking the jQM 1.0.1 docs:
http://jquerymobile.com/demos/1.0.1/docs/api/globalconfig.html
Here is a example:
http://jquerymobile.com/demos/1.0.1/docs/config/pageLoadErrorMessage.html ( click the "or Try this broken link" button )
Now if you have the ability to upgrade jQM to 1.1.1 you might try something like this:
//use theme swatch "b", a custom message, and no spinner
$.mobile.showPageLoadingMsg("b", "Please check your net connection", true);
// hide after delay
setTimeout( $.mobile.hidePageLoadingMsg, 1500 );
Docs:
http://jquerymobile.com/demos/1.1.1/docs/api/methods.html
UPDATE:
Another thought is to use a plugin to achieve something like you want, Does something like this work?
http://dev.jtsage.com/jQM-SimpleDialog/demos/bool.html
Simply use:
$(document).bind("mobileinit", function(){
$.mobile.pageLoadErrorMessage("Please check your netconnection");
});
http://jquerymobile.com/test/docs/api/globalconfig.html
Set the pageLoadErrorMessage as described here http://jquerymobile.com/demos/1.1.1/docs/api/globalconfig.html
EDIT
If you want to handle the behaviour in a custom way, set loadingMessage to false. This prevents the loading message from being displayed. You can bind to the pageloadfailed (described here http://jquerymobile.com/demos/1.1.1/docs/api/events.html) and add add your custom handling logic in the event handler.
I'd like to learn how to bind a CNRTL-S or COMMAND-S to call a function that I have on my page which AJAX saves the textarea content's
How can I bind those two commands? I used to use the following when it was just a textarea, but since adding TinyMCE it no longer works. Suggestions?
// Keybind the Control-Save
jQuery('#text_area_content').bind('keydown', 'ctrl+s',function (evt){
saveTextArea();
return false;
});
// Keybind the Meta-Save Mac
jQuery('#text_area_content').bind('keydown', 'meta+s',function (evt){
saveTextArea();
return false;
});
Thanks
To use a custom method for saving, i declare my saving function in the tinymce.init method
tinyMCE.init({
// General options
mode: "none",
/* some standard init params, plugins, ui, custom styles, etc */
save_onsavecallback: saveActiveEditor,
save_oncancelcallback: cancelActiveEditor
});
Then i define the function itself
function saveActiveEditor() {
var activeEditor = tinyMCE.activeEditor;
var saveUrl = "http://my.ajax.path/saveStuff";
var idEditor = activeEditor.id;
var contentEditor = activeEditor.getContent();
/* the next line is for a custom language listbox to edit different locales */
var localeEditor = activeEditor.controlManager.get('lbLanguages').selectedValue;
$.post(saveUrl ,
{ id: idEditor, content: contentEditor, locale: localeEditor },
function(results) {
if (results.Success) {
// switch back to display instead of edit
return false;
}
else {
activeEditor.windowManager.alert('Error saving data');
return false;
}
},
'json'
);
return false;
}
Don't forget to return false to override the default save action that posts back your data to the server.
edit to add: i only let the user change one tinymce instance at a time. You may want to change the locating the current instance to something else :)
edit #2: TinyMce already catches the Ctrl+s binding to process the data. Since it also cleans up html and is able to handle specific rules it's given when saving, the solution i propose is to plug your way of saving in tinyMce instead of fully overriding the Ctrl+s binding
The problem here is that the tinymce iframe does not delegate the events to the parent window. You can define custom_shortcuts in tinymce and/or use the following syntax:
// to delegate it to the parent window i use
var create_keydown_event = function(combo){
var e = { type : 'keydown' }, m = combo.split(/\+/);
for (var i=0, l=m.length; i<l; i++){
switch(m[i]){
case 'ctrl': e.metaKey = true;
case 'alt': case 'shift': e[m[i] + 'Key'] = true; break;
default : e.charCode = e.keyCode = e.which = m[i].toUpperCase().charCodeAt(0);
}
}
return e;
}
var handler = function(){
setTimeout(function(){
var e = create_keydown_event(combo);
window.parent.receiveShortCutEvent(e);
}, 1);
}
//ed.addShortcut(combo, description, handler);
ed.addShortcut('ctrl+s', 'save_shortcut', handler);
in the parent window you need a function receiveShortCutEvent which will sort out what to do with it