I'm trying to use a yui plugin that pulls from a json file and populates a div on the page. Everything should be a go, however, since the plugin never gets to the render stage, the rest of it does not run. It is successfully loaded otherwise (if I stick an alert or console.log at the beginning of the event, it works fine).
Here's the code:
YUI.add('events', function(Y) {
var urlEvents = //"/cgi-bin/eventview-json/?cal=admissions/events&days=10";
"/admissions/events/events.json";
//var eventContainer = $("#insert-events");
/* EventList class constructor */
var EventList = function(config) {
EventList.superclass.constructor.apply(this, arguments);
};
/*
* Required NAME static field, to identify the class and
* used as an event prefix, to generate class names etc. (set to the
* class name in camel case).
*/
EventList.NAME = "EventList";
/*
* Required NS static field, to identify the property on the host which will,
* be used to refer to the plugin instance ( e.g. host.feature.doSomething() )
*/
EventList.NS = "EventList";
/*
* The attribute configuration for the plugin. This defines the core user facing state of the plugin
*/
EventList.ATTRS = {};
var convertYYYYMMDDtoJS = function(s) {
var a, jsdate = null;
try {
a = /^(\d\d\d\d)(\d\d)(\d\d)$/.exec(s);
if (a) {
jsdate = new Date(a[1], a[2]-1, a[3]);
}
} catch (ex) {
/* Nothing */
}
return jsdate;
};
var insertEvents = function(id, response, e) {
alert('hello');
var i, resp, events, event, html, jsdate, label, seenevent, yyyymmdd;
var maxevents = 5, eventcount;
try {
resp = Y.JSON.parse(response.responseText);
events = resp.results;
html = "";
seenevent = {};
eventcount = 0;
yyyymmdd = "";
for (i = 0; i < events.length; i++) {
event = events[i];
if (seenevent[event.title]) {
continue;
}
seenevent[event.title] = true;
if (event.date !== yyyymmdd) {
// This is the first event on this date.
// If we've seen maxevents events, then bail before starting a new day.
if (eventcount >= maxevents) {
break;
}
// Put out a new label for this day.
jsdate = convertYYYYMMDDtoJS(event.date);
label = Y.DataType.Date.format(jsdate, {format: "%b %e %a"});
/*
* The first empty div below, "<div class='clear'></div>" is only needed for IE 7.
* IE 7 does not properly clear both left and right floats when "clear: both" is specified
* if the element itself is floated. The extra div clears the floats, but isn't floated
* itself. The extra div doesn't cause any grief in newer browsers, so I add it always.
*/
html += "<div class='clear'></div><div class='event-datelabel'>" + label + "</div>\n";
yyyymmdd = event.date;
}
html += "<div class='event-text'>" + event.html + "</div>\n";
eventcount++;
}
this.get('host').setContent(html + "<div id='events-footer'><a href='/calendar/'>all events</a></div>");
} catch(ex) {
console.log("Error", ex);
this.get('host').setContent("Event list not available");
return;
}
};
var insertEventList = function(yyyy, mm, dd) {
var url = urlEvents;
if (yyyy) {
url += '&yyyy=' + yyyy;
}
if (mm) {
url += '&mm=' + mm;
}
if (dd) {
url += '&dd=' + dd;
}
Y.io(url, {on: {success: insertEvents}, context: this});
};
/* EventList extends the base Plugin.Base class */
Y.extend(EventList, Y.Plugin.Base, {
render: function() {
insertEventList.call(this);
}
});
//console.log("assigning", EventList);
Y.namespace("Plugin").EventList = EventList;
}, '1.0.0' ,{requires:['node', 'base', 'plugin', "json-parse", "datatype-date"]});
Here's the excerpt from the code with the render bit:
Y.extend(EventList, Y.Plugin.Base, {
render: function() {
insertEventList.call(this);
}
Admittedly, YUI3 confuses me, and I would prefer other libraries, but I don't have a choice in this situation. There's likely one thing that I've just completely looked over.
Thanks guys
I've used YUI3 plugins before and they are a bit difficult to grasp, but I'll try to help if I can. Once you've created the plugin, which, from what I can tell, you've already done so successfully, you plug it into an object somewhere else in your code:
someObj.plug(Y.Plugin.EventList, cfg);
After that, you can access the plugin's methods from within the object's plugin namespace. In your case you'd do this like so:
someObj.EventList.render();
Hopefully I'm understanding your question correctly and I hope that helps clear some stuff up for you. Good luck! :)
Related
This script basically makes it so that it deletes content from specific users from a forum. It is working as intended but there is still a left over element (table) from the deleted content. Any ideas are highly appreciated.
Code:
https://greasyfork.org/scripts/2690-fitmisc-total-ignore/code
// ==UserScript==
// #name Fitmisc_Total_Ignore
// #author Arris
// #description This script is designed to completly eradicate from sight the worst posters on Fitmisc.com
// #include http://fitmisc.com/*
// #namespace http://fitmisc.com/
// #version 0.9
// ==/UserScript==
function canIgnore(sUser) {
if( sUser.match(/niko/i) )
return true;
if( sUser.match(/thesavagepony/i) )
return true;
if( sUser.match(/Lloyd Banks/i) )
return true;
if( sUser.match(/Lil B/i) )
return true;
if( sUser.match(/Round-Mound/i) )
return true;
return false;
}
function setIgnoreThread() {
var a; var s;
a=document.evaluate(
"//div[starts-with(#class, 'threadmeta')]",
document,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null);
for (var i=0; i<a.snapshotLength; i++) {
s=a.snapshotItem(i).innerHTML;
if( canIgnore(s) ) {
//a.snapshotItem(i).parentNode.parentNode.parentNode.style.display = 'none';
a.snapshotItem(i).parentNode.parentNode.parentNode.innerHTML = '';
}
}
}
function setIgnorePost() {
var a; var s;
a=document.evaluate(
"//div[starts-with(#class, 'username_container')]",
document,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null);
for (var i=0; i<a.snapshotLength; i++) {
s=a.snapshotItem(i).innerHTML;
if( canIgnore(s) ) {
//a.snapshotItem(i).parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.style.display = 'none';
a.snapshotItem(i).parentNode.parentNode.parentNode.innerHTML = '<li class="postbitlegacy postbitim postcontainer old" style="background:white;border-color:white;"></li>';
}
}
}
function setIgnoreQuote() {
var a; var s;
a=document.evaluate(
"//div[starts-with(#class, 'bbcode_postedby')]",
document,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null);
for (var i=0; i<a.snapshotLength; i++) {
s=a.snapshotItem(i).innerHTML;
if( canIgnore(s) ) {
//a.snapshotItem(i).parentNode.parentNode.parentNode.style.display = 'none';
a.snapshotItem(i).parentNode.innerHTML = '';
}
}
}
if(window.opera) { //opera only
(function(){
document.addEventListener('DOMContentLoaded', function() {
setIgnoreThread();
setIgnorePost();
setIgnoreQuote();
}, false);
})()
} else {
setIgnoreThread();
setIgnorePost();
setIgnoreQuote();
}
Instead of hardcode the usernames each in a dedicated regulare expression consider a generation of a single expression matching any item of an array of ignored user names. Use the RegExp.test() method when you don't need to store a result.
(I've removed user names in my examples, except the anonymous operator account "OP". I don't like defamation in the public.)
var
ignoredUsers = ['user 1', 'another user', 'OP', 'yet another user']
;
function canIgnore(sUser)
{
return new RegExp(ignoredUsers.join('|'), 'i').test(sUser);
}
However, there's no need to extract user names, check them against regular expressions and do further processing, since you are using XPath expressions which can find content and step back in the DOM tree. They are capable to retrieve all text nodes with one of a list of users inside a container class and give back their ancestors ([grand]parents) selected by a class.
// = =UserScript= =
// #name Fitmisc_Total_Ignore
// #author Arris
// #description This script is designed to completly eradicate from sight the worst posters on Fitmisc.com
// #include http://fitmisc.com/*
// #namespace http://fitmisc.com/
// #grant GM_xpath
// #version 0.9
// = =/UserScript= =
var
// TODO: GM_ get/set Value as JSON ; put users into igno list by mouse click
ignoredUsers = ['user 1', 'another user', 'OP', 'yet another user']
;
function removeContents(ignoreList)
{
if(ignoreList instanceof Array && ignoreList.length)
GM_xpath
( { path: "//div[#class='username_container']//*[text() = '"
+ ignoreList.map(function(user) { return user.replace("'", "\\'"); } ).join("' or text() = '")
+ "']/ancestor::li[contains(#class, 'postcontainer')]",
all :true
}
).forEach(function(elem){ elem.parentElement.removeChild(elem); });
}
removeContents(ignoredUsers);
Since users accounts could be renamed, consider to filter user id, e.g. in the href attribute of profile link or photo, instead of relying on changable names.
The expample above does the job on thread posts. You can combine multiple XPath expressions into one by the "|" char.
Im using this code:
jQuery.fn.update_textarea = function(test) {
$("#articles_textarea").html('');
for (i=0;i<test;++i) {
if (message[i]) { $("#articles_textarea").html('<textarea></textarea>'); }
else { message[i] = ''; $("#articles_textarea").html('<textarea></textarea>'); }
}
}
When im trying to add more then one, nothing happends but if i add 1 it works just as it should..
This is the "call"code
$("#articles_textarea").update_textarea(total);
This is variable total:
var total = parseFloat($(".testCounter").val()) + 5;
This is for calling only one textarea (the part that works):
$("#articles_textarea").update_textarea(1);
When i call one box it's working when i call several boxes nothing happends at all..
try with append method. .html() will remove the previous content
jQuery.fn.update_textarea = function(test) {
$("#articles_textarea").html('');
for (i=0;i<test;++i) {
if (message[i]) { $("#articles_textarea").append('<textarea></textarea>'); }
else { message[i] = ''; $("#articles_textarea").append('<textarea></textarea>'); }
}
}
Why you use jQuery.fn, when you don't use its functionality, whereas it works, only for the textareas with articles_textarea Id, you better do it using a simple function like:
function update_textarea(test) {
$("#articles_textarea").html('');
for (var i=0;i<test;++i) {
if (!message[i]){
message[i] = '';
}
$("#articles_textarea").append('<textarea></textarea>');
}
};
adding a new function to jQuery.fn is used to create new jQuery plugins, check this link for more info.
the other point was not using var i in your for loop which is not a good practice. Moreover both your if statement and the else, were the same, except message[i] = ''; part.
BTW if you want to do it using jQuery.fn, you better do it like:
jQuery.fn.update_textarea = function(test) {
this.html('');
for (var i=0;i<test;++i) {
if (!message[i]){
message[i] = '';
}
this.append('<textarea></textarea>');
}
};
I am learning JQuery. I have a need to create a custom control. This control is going to basically render some HTML. Sometimes, I want to just get the HTML. My hope is to use the following syntax:
// Put generated html inside of "myElement". "myElement" is a div element.
$("#myElement").myPlugin({ value: 10 });
// Retrieve the html that myPlugin would place of a div element.
// Basically, I want the javascript equivalent of a C# static function here.
// But I think the following approach is wrong:
var html = myPlugin().getHtml(10);
alert(html);
In an attempt to accomplish this, I'm using the following:
(function ($) {
$.fn.myPlugin = function (element, options) {
var defaults = { theValue: 0 }
var plugin = this;
plugin.settings = {};
var $element = $(element),
element = element;
plugin.init = function () {
plugin.settings = $.extend({}, defaults, options);
var html = createHtml(defaults.theValue);
$(element).html(html);
}
plugin.getHtml = function (v) {
return createHtml(v);
}
var createHtml(v) {
return "<span>" + v + "</span>";
}
})(jQuery);
I have two problems: 1) I can see that the HTML is being generated, but it does not appear to be added to the DOM. 2) I can't statically call the function. How can I make my function statically visible?
Thank you!
There are a few tweaks you need to make:
(function($) {
$.fn.myPlugin = function(options) {
var defaults = {
theValue: 0
},
settings = $.extend({}, defaults, options);
this.html($.myPlugin.getHtml(settings.theValue));
};
$.myPlugin = {
getHtml: function(value) {
return "<span>" + value + "</span>";
}
};
})(jQuery);
Usage:
$("#foo").myPlugin({ theValue: 10 });
var html = $.myPlugin.getHtml(10);
First off, you were never calling the init method, so nothing was going to work. It isn't going to be called automatically. Perhaps you were thinking of the jQueryUI widget factory?
Additionally, element doesn't get passed to the plugin, this refers the the element the plugin was called on. You only get passed options.
Finally, to create a static jQuery method, just attach it right to $.
Example: http://jsfiddle.net/xF2S6/
I come from the land of Java, C#, etc. I am working on a javascript report engine for a web application I have. I am using jQuery, AJAX, etc. I am having difficulty making things work the way I feel they should - for instance, I have gone to what seems like too much trouble to make sure that when I make an AJAX call, my callback has access to the object's members. Those callback functions don't need to be that complicated, do they? I know I must be doing something wrong. Please point out what I could be doing better - let me know if the provided snippet is too much/too little/too terrible to look at.
What I'm trying to do:
On page load, I have a select full of users.
I create the reports (1 for now) and add them to a select box.
When both a user and report are selected, I run the report.
The report involves making a series of calls - getting practice serieses, leagues, and tournaments - for each league and tournament, it gets all of those serieses, and then for each series it grabs all games.
It maintains a counter of the calls that are active, and when they have all completed the report is run and displayed to the user.
Code:
//Initializes the handlers and reports
function loadUI() {
loadReports();
$("#userSelect").change(updateRunButton);
$("#runReport").click(runReport);
updateRunButton();
return;
$("#userSelect").change(loadUserGames);
var user = $("#userSelect").val();
if(user) {
getUserGames(user);
}
}
//Creates reports and adds them to the select
function loadReports() {
var reportSelect = $("#reportSelect");
var report = new SpareReport();
engine.reports[report.name] = report;
reportSelect.append($("<option/>").text(report.name));
reportSelect.change(updateRunButton);
}
//The class that represents the 1 report we can run right now.
function SpareReport() {
this.name = "Spare Percentages";
this.activate = function() {
};
this.canRun = function() {
return true;
};
//Collects the data for the report. Initializes/resets the class variables,
//and initiates calls to retrieve all user practices, leagues, and tournaments.
this.run = function() {
var rC = $("#rC");
var user = engine.currentUser();
rC.html("<img src='/img/loading.gif' alt='Loading...'/> <span id='reportProgress'>Loading games...</span>");
this.pendingOperations = 3;
this.games = [];
$("#runReport").enabled = false;
$.ajaxSetup({"error":(function(report) {
return function(event, XMLHttpRequest, ajaxOptions, thrownError) {
report.ajaxError(event, XMLHttpRequest, ajaxOptions, thrownError);
};
})(this)});
$.getJSON("/api/leagues", {"user":user}, (function(report) {
return function(leagues) {
report.addSeriesGroup(leagues);
};
})(this));
$.getJSON("/api/tournaments", {"user":user}, (function(report) {
return function(tournaments) {
report.addSeriesGroup(tournaments);
};
})(this));
$.getJSON("/api/practices", {"user":user}, (function(report) {
return function(practices) {
report.addSerieses(practices);
};
})(this));
};
// Retrieves the serieses (group of IDs) for a series group, such as a league or
// tournament.
this.addSeriesGroup = function(seriesGroups) {
var report = this;
if(seriesGroups) {
$.each(seriesGroups, function(index, seriesGroup) {
report.pendingOperations += 1;
$.getJSON("/api/seriesgroup", {"group":seriesGroup.key}, (function(report) {
return function(serieses) {
report.addSerieses(serieses);
};
})(report));
});
}
this.pendingOperations -= 1;
this.tryFinishReport();
};
// Retrieves the actual serieses for a series group. Takes a set of
// series IDs and retrieves each series.
this.addSerieses = function(serieses) {
var report = this;
if(serieses) {
$.each(serieses, function(index, series) {
report.pendingOperations += 1;
$.getJSON("/api/series", {"series":series.key}, (function(report) {
return function(series) {
report.addSeries(series);
};
})(report));
});
}
this.pendingOperations -= 1;
this.tryFinishReport();
};
// Adds the games for the series to the list of games
this.addSeries = function(series) {
var report = this;
if(series && series.games) {
$.each(series.games, function(index, game) {
report.games.push(game);
});
}
this.pendingOperations -= 1;
this.tryFinishReport();
};
// Checks to see if all pending requests have completed - if so, runs the
// report.
this.tryFinishReport = function() {
if(this.pendingOperations > 0) {
return;
}
var progress = $("#reportProgress");
progress.text("Performing calculations...");
setTimeout((function(report) {
return function() {
report.finishReport();
};
})(this), 1);
}
// Performs report calculations and displays them to the user.
this.finishReport = function() {
var rC = $("#rC");
//snip a page of calculations/table generation
rC.html(html);
$("#rC table").addClass("tablesorter").attr("cellspacing", "1").tablesorter({"sortList":[[3,1]]});
};
// Handles errors (by ignoring them)
this.ajaxError = function(event, XMLHttpRequest, ajaxOptions, thrownError) {
this.pendingOperations -= 1;
};
return true;
}
// A class to track the state of the various controls. The "series set" stuff
// is for future functionality.
function ReportingEngine() {
this.seriesSet = [];
this.reports = {};
this.getSeriesSet = function() {
return this.seriesSet;
};
this.clearSeriesSet = function() {
this.seriesSet = [];
};
this.addGame = function(series) {
this.seriesSet.push(series);
};
this.currentUser = function() {
return $("#userSelect").val();
};
this.currentReport = function() {
reportName = $("#reportSelect").val();
if(reportName) {
return this.reports[reportName];
}
return null;
};
}
// Sets the enablement of the run button based on the selections to the inputs
function updateRunButton() {
var report = engine.currentReport();
var user = engine.currentUser();
setRunButtonEnablement(report != null && user != null);
}
function setRunButtonEnablement(enabled) {
if(enabled) {
$("#runReport").removeAttr("disabled");
} else {
$("#runReport").attr("disabled", "disabled");
}
}
var engine = new ReportingEngine();
$(document).ready( function() {
loadUI();
});
function runReport() {
var report = engine.currentReport();
if(report == null) {
updateRunButton();
return;
}
report.run();
}
I am about to start adding new reports, some of which will operate on only a subset of user's games. I am going to be trying to use subclasses (prototype?), but if I can't figure out how to simplify some of this... I don't know how to finish that sentence. Help!
$.getJSON("/api/leagues", {"user":user}, (function(report) {
return function(leagues) {
report.addSeriesGroup(leagues);
};
})(this));
Can be written as:
var self = this;
$.getJSON("/api/leagues", {"user":user}, (function(leagues) {
self.addSeriesGroup(leagues);
});
The function-returning-function is more useful when you're inside a loop and want to bind to a variable that changes each time around the loop.
Provide "some" comments where necessary.
I'm going to be honest with you and say that I didn't read the whole thing. However, I think there is something about JavaScript you should know and that is that it has closures.
var x = 1;
$.ajax({
success: function () {
alert(x);
}
});
No matter how long time it takes for the AJAX request to complete, it will have access to x and will alert "1" once it succeeds.
Understand Closures. This takes some getting used to. (which, many will use, and is certainly the typical way of going about things, so it's good if you understand how that's happening)
This is a good thread to read to get a simple explanation of how to use them effectively.
You should use prototypes to define methods and do inheritance:
function Parent(x) {
this.x = x; /* Set an instance variable. Methods come later. */
}
/* Make Parent inherit from Object by assigning an
* instance of Object to Parent.prototype. This is
* very different from how you do inheritance in
* Java or C# !
*/
Parent.prototype = { /* Define a method in the parent class. */
foo: function () {
return 'parent ' + this.x; /* Use an instance variable. */
}
}
function Child(x) {
Parent.call(this, x) /* Call the parent implementation. */
}
/* Similar to how Parent inherits from Object; you
* assign an instance of the parent class (Parent) to
* the prototype attribute of the child constructor
* (Child).
*/
Child.prototype = new Parent();
/* Specialize the parent implementation. */
Child.prototype.foo = function() {
return Parent.prototype.foo.call(this) + ' child ' + this.x;
}
/* Define a method in Child that does not override
* something in Parent.
*/
Child.prototype.bar = function() {
return 'bar';
}
var p = new Parent(1);
alert(p.foo());
var ch = new Child(2);
alert(ch.foo());
alert(ch.bar());
I'm not familiar with jQuery, but I know the Prototype library (worst name choice ever) has some functionality that make it easier to work with inheritance.
Also, while coming up with the answer to this question, I found a nice page that goes into more detail on how to do OO right in JS, which you may want to look at.
I am building a drag'n'drop gui builder in Javascript. So far so good.
As I add items to the GUI and configure them; I have two mechanisms for addressing them:
the 'class' - which I use for doing things to all instances of an item (eg CSS, generic functionality and so on and so forth) and which I can bind javascript libraries to... and I can make full use of polymorphic class names (ie class="name1 name2 name3 name4" with different things bound to each class name...)
the 'id' - which refers to this particular instance of a text box or a paragraph and which I can bind javascript libraries to
My problem is this: the 'id' must be unique across all html items on the page (by definition) so how do I ensure this? I need to get all the id's of all the items and then maintain some sort of state table.
Starting from a blank bit of html this is pretty reasonable - but I need to start from a partly created bit of html with a mixture of existing 'id's - some of which will be in my unique scheme and some of which wont be...
The way to do this best ought to be a solved problem.
Suggestions, tips, examples?
The best way to do this will depend entirely upon the structure and organization of your javascript. Assuming that you are using objects to represent each of your GUI elements you could use a static counter to increment your ids:
// Your element constructor
function GuiElement() {
this.id = GuiElement.getID();
}
GuiElement.counter = 0;
GuiElement.getID = function() { return 'element_' + GuiElement.counter++; };
Of course you probably have more than one type of element, so you could either set each of them up so that they have their own counter (e.g. form_1, form_2, label_1, label_2) or so that they all share a counter (e.g. element_1, element_2, element_3), but either way you will probably want them to inherit from some base object:
// Your base element constructor
function GuiElement(tagName, className) {
this.tagName = tagName;
this.className = className;
}
GuiElement.counter = 0;
GuiElement.getID = function() { return 'element_' + GuiElement.counter++; };
GuiElement.prototype.init = function() {
this.node = document.createElement(this.tagName);
this.node.id = this.id = GuiElement.getID();
this.node.className = this.className;
}
// An element constructor
function Form() {
this.init();
}
Form.prototype = new GuiElement('form', 'form gui-element');
// Another element constructor
function Paragraph() {
this.init();
}
Paragraph.prototype = new GuiElement('p', 'paragraph gui-element');
You could also go this route if you would rather keep some variables "private":
// Your element constructor constructor
var GuiElement = (function() {
var counter = 0;
function getID() {
return 'element_' + counter++;
}
return function GuiElement(tagName, className) {
return function() {
this.node = document.createElement(tagName);
this.node.id = this.id = getID();
this.node.className = className + ' gui-element';
this.className = className;
};
}
})();
// Create your element constructors
var Form = GuiElement('form', 'form'),
Paragraph = GuiElement('p', 'paragraph');
// Instantiate elements
var f1 = new Form(),
f2 = new Form(),
p1 = new Paragraph();
Update: If you need to verify that an id is not already in use then you could add the check you and of the getID methods:
var counter = 0;
function getID() {
var id = 'element_' + counter++;
while(document.getElementById(id)) id = 'element_' + counter++;
return id;
}
function uniqueId() {
return 'id_' + new Date().getTime();
}
If you happen to be using the Prototype library (or want to check it out), you can use the Element.identify() method.
Otherwise, Darin's response is a good idea as well.
function generateId() {
var chars = "0123456789abcdefghiklmnopqrstuvwxyz",
string_length = 8,
id = '';
for (var i = 0; i < string_length; i++) {
var rnum = Math.floor(Math.random() * chars.length);
id += chars.substring(rnum, rnum + 1);
}
return id;
}
Close enough to unique is good enough. Don't use the Date() solution unless you're only generating a single ID at any given time...