HTML5 Data attribute with complex JSON object and javascript - javascript

I came across a strange requirement (set by myself)...
I'm creating an easy to integrate ajax content loader plugin with lots of options and callbacks. Since the loader is a class and the developer can have multiple instances on a single page, I wanted to get rid of all the ugly code required for every single initialization and decided to use data attributes instead - they look awesome and proficient!
The question is: How to add functions and javascript in general inside a data attribute?
Example:
var url = "someurl/goes/here/";
var Template = new TemplateEngine('Name', {
onCreate: function(template, parts) {
// do something with template parts
template.ID += 1;
},
onRender: function(template, parts) {
template.addClass('flash');
}
});
var settings = {
container: DOM_ELEMENT|STRING,
template: Template,
disableDefaultRender: true,
// a bunch of hooks and callbacks like this:
onBeforeRequest: function(loader, data) {
new_data = data;
// modify request data somehow
loader.requestData = new_data;
},
onRender: function(loader, data) {
loader.renderData(data, function(part) {
// define specific rendering logic for different template parts
// in required
});
},
onAfterRequest: function(loader, data) {
},
onError: function(loader, data) {
}
// etc, etc
};
var THE_LOADER = new SuperFancyAjaxLoader(url, settings);
My original idea is to somehow put all of the above inside the said data attribute:
<div data-fancy-stuff="{all-or-most-of-the-above}">more stuff</div>
and make the script itself find all elements and initialize instances for each of them like so:
var elements = document.querySelector('[data-fancy-stuff]');
for(item in elements) {
try {
var data = elements[item].getAttribute('data-fancy-stuff');
var THE_LOADER = new SuperFancyAjaxLoader(data.url, data.settings);
} catch (ex) {
console.log('Someone messed with prototypes');
}
}
Is the idea of putting javascript functions inside an attribute idiotic? Or is there a way to actually put some js inside an attribute?
I understand that if there's so much javascript required, it's pointless to try and put it inside an attribute, but in real life cases (for this particular task), I will have 3-5 content loaders per page, most of them (or all) will use the same template and rendering logic, but they will all have to modify the request data differently by themselves.
p.s. Eval is Evil.
edit: I'm open to design proposals which do not involve third party MVC frameworks.

May be I don't understand well, but You want provide some JavaScipt modules/classes/objects through HTML5 attribute???
I think it's bad design. It's seems to be mixin of distinct layers.
So technically U have just ONE ability - to call eval, even after your PS because eval is the only point where JavaScript can get other JavaScript from String - ONLY.
But if U want dynamically load some complex javascript as reaction to data in some elements it's very good idea to learn and apply most ultimate thing for such scenarios - well-old-knownn require.js http://requirejs.org/. And if you want hardly bind DOM with some data and behavior you must to learn some of MVC JavaScript solutions - AngularJS, Backbome, Amber and so on.
By design u have to split your application to presentation layer where DOM will live and business layer were JavaScript will live. To bind them to each other you use string/JSON descriptors in DOM attribute and load JavaScript dynamically using on-fly head rewriting or by XHR+eval, such design is asynchronous, quick and is main choise of all solid network-based applications from gmail to all-other-cool-staff. To help build application with such model - require.js is best and most known helper.

Related

html iframes vs jquery.load() when adding content dynamically (no php, etc.) [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 9 years ago.
Improve this question
I am working on a project where I need to dynamically build up web content coming from different sources (same domain). All the main libraries and css are managed centrally but depending on a configuration I have to add pages (html and embedded JavaScript) as 'tab content'.
My first approach was using iframes and it worked well besides the annoyance that I had some duplicte code (especially the header). I also had some issues with frame sizes but I could handle that in the end.
Now of course I read that iframes are evil but almost all the alternatives I found were using php or something else server-side. Unfortunately my web server does not provide anything like that, so I came across jQuery's load function. My first attempt is satisfying but what unsettles me now is managing (global) variables (and functions). Because jQuery.load() simply inserts the code into the DOM I always need to be extremely careful about naming here. For example I usually have a function called init() which is included in the body for the onload-Event. But every other page will need one of those as well.
The project is very likely to grow (maybe at some point even external developers will take part). I am now at a point where I have to decide which path to take and I am torn.
So my Question: Even though iframes are evil, im my experience it is a lot easier and secure than inserting the code via jQuery (in this particular case). Oh, that was not a question... The question would be: Is there any way to encapsulate variables and functions into the content that I load with jQuery? Or might there be a better way of handling variables/functions in this case anyways.
I am grateful for any suggestions.
I don't want to suggest to do it this way, but because you want to to know how this can be done, here some code to illustrate how it could be done (only rudimentary tested and should not be used without further testing).
While I'm not sure if you could (and i think you should not) structure your project that way i hope that it will help to solve your problem.
The main code of your page:
var app = {};
(function() {
var nextId = 1;
var elementById = {};
app.registerContent = function( id, initFunction ) {
try {
initFunction(elementById[id]);
} catch( e ) {
//if an error happend in the init script catch it so that the script will not break
console.error(e);
}
//remove the jquery element from the list
delete elementById[id];
}
//custom loader to handle init script
jQuery.fn.customLoader = function(url) {
var elm = $(this);
var id = nextId++;
//store the element where the data is loaded to in a list
elementById[id] = elm;
$.get(url,function(data) {
//replace the id in the script so that the element the data is loaded into
// can be passed to the init script later.
data = data.replace("%%unique-id%%",id);
elm.html(data); //append the data to the element
});
}
//a sample how you would load the content
$(function() {
$(".dest").customLoader("content.html");
});
})();
The code in your requested data:
<div>some content</div>
<!-- the script that correspons to that part -->
<script>
//create a scope using a function that is executed directly
(function() {
//using var to make the variables only visible to this place
var uniqueId = %%unique-id%%; //this will be replaced by the loader to identify the element where the data is loaded to
var someVar = 1;
app.registerContent(uniqueId, initFunctionForTheContent)
function initFunctionForTheContent(element) {
element.css("background-color", "red")
}
})();
</script>
Not having a back end with a huge app and many developers sounds like hell. Sometimes things are best fitted on the server side e.g php's include.
Either way I would go with an MVC solution.
I have experience with backbone and underscore so I will give an example with that but there are many other MVC solutions.
If all you need to do is load some data you can use underscores templating which is very lightweight
You can do stuff like:
In the model:
sum: function() {
Sum certain properties of your objects.
}
In index.html
<script type="text/template" id="sum-template">
sum: <% print(sum) %>
</script
In the view
sumTemplate: _.template($('#sum-template').html()),
this.$el.find('#sum').html(this.sumTemplate({sum:Expenses.sum()}));
As you can see you can get data from different places and load them to certain HTML elements. It easy to manage on the long run.
In order to persist data you can free and paid hosted data bases a simple google search for
redis hosting, mongodb hosting etc... so you are not dependent on your current server.

Is saving elements as object properties good practice?

I'm writing a small JavaScript framework for fun and possible implementation similar to backbone(hence the tag). I've started saving elements as object properties, as shown below. I'm not sure if I've seen this done, so I was curious if this causes any issues.
Similarly, If the module depends on other modules I list those at the top of the object in the form of....another object.
I wanted a way to list dependencies ( page elements or JavaScript modules ) and detect any issues up front. This has similar ( not same ) benefits as dependency injection.
This is a specific question on this code review post which explains a bit further on how the framework works.
/*MUserTry
**
**
**
*/
$A.modelAjax({
Name: 'MUserTry',
S: {
DynSma: SDynSma,
DynTwe: SDynTwe,
DynArc: SDynArc,
AniFlipPage: SAniFlipPage,
ClientStorage: SClientStorage
},
E: {
but: $A('#ut_but')[0]
},
J: {
box: $('#ut_box')
},
init: function () {
var pipe = {},
this_hold = this;
this.J.box.draggable();
this.E.but.addEventListener("click", function () {
pipe = $A.definePipe(this_hold.Name);
$A.ajaxMachine(pipe);
}, false);
},
pre: function (pipe) {
pipe.page.email = this.e_button.getAttribute('data-email');
pipe.proceed = true;
},
post: function (pipe) {
this.S.ClientStorage.setAll(pipe.server.smalls);
this.S.DynSma.run(pipe.server.smalls);
this.S.DynArc.run(pipe.server.arcmarks);
this.S.DynTwe.run(pipe.server.tweets);
this.S.AniFlipPage.run('ma');
},
finish: function (pipe) {
$A.log(pipe);
}
});
Ok first off let me offer the obligatory "you'll never get a better wheel by re-inventing the wheel" warning. Whatever you're trying to accomplish, you're almost certainly going to be more successful with it if you use an existing library. And even if there is good cause for you to make your own, it would still benefit you immensely to at least look at existing libraries.
But ... maybe you're just having fun with this project, and looking at other projects isn't fun so you're not doing it. Fair enough.
In any case, if you do look at Backbone, you'll find that this practice is core to the Backbone View class. Every View in Backbone has an "el" and "$el" property, which refer to the raw DOM element for the view and the jQuery-wrapped version of that element.
Backbone has no real performance issues with this because variables/properties in JS are just pointers; in other words, when you set the property of an object to an element, you aren't duplicating that element, you're just adding a reference to it (to put it another way, it's more like you're an A tag rather than a whole new document).
The one time Backbone does have a problem though (and your framework will too) is with stale references. In other words, if you just remove element X from the page, the browser will stop using memory for it (eventually, via garbage collection). But if there is an object out there which points to that element, it won't get garbage-collected, because anything with a reference isn't "garbage".
So, the main thing you have to watch out for is making sure that these objects either:
A) get deleted whenever their elements do, or
B) get rid of their references (eg. delete obj.reference) when their elements get deleted
If you don't do that, things will still probably work just fine ... until you use it on a page with lots of elements being created/deleted, at which point Firefox will start popping up "this script took way too long to run, are you really sure you want to be doing this?" messages.

What options are there for building html from an ajax response?

What options or libraries are out there for building html from a returned ajax response?
Currently I am taking the json data I receive, building the html as a string, and using a jQuery DOM insertion function, but I have to think there is a better and more maintainable way out there to do what I am trying to do.
For example, I have a page with a list of projects, with just a thumbnail and title, which users can click on. Once a project is clicked and the json request comes back successfully, more detailed project information is shown. Currently I am doing something that is essentially:
build_project = function(data){
var projectHTML = "<div id='projectData_"+data.id+"'>"+data.contents+"</div>";
return projectHTML;
}
Then inserting it into the DOM where it needs to go. The problem arises when there is more than just one element, sometimes I'll have to create up to 6-10 different nested elements with stuff like the below cropping up:
projectHTML += "</div>";
projectHTML += "</div>";
projectHTML += "</div>";
I'm aware of mustache.js, but I'm not sure if that is exactly what I'm looking for or not - and whether there are any other options that I should investigate.
Thanks!
You are on the right track. Take a look at the jquery.tmpl library.
http://api.jquery.com/jquery.tmpl/
You can try http://knockoutjs.com/ alongside with JQuery templates.
"Knockout is a JavaScript library that helps you to create rich, responsive display and editor user interfaces with a clean underlying data model. Any time you have sections of UI that update dynamically (e.g., changing depending on the user’s actions or when an external data source changes), KO can help you implement it more simply and maintainably."
You can make a printf style method in JavaScript.
You can live dangerously and extend the String object (as luck would have it I wrote this a couple of days ago)...
String.prototype.printf = function() {
var theseArguments = Array.prototype.slice.call(arguments).reverse(),
argumentsLength = theseArguments.length,
str;
str = this.replace(/%s/g, function() {
return theseArguments[--argumentsLength];
});
return str;
};
jsFiddle.
...or simply make it a normal function, and perhaps faux namespace it as util.printf = function() { ... }.
Of course, the function above isn't really printf() (it handles string interpolation only), but you could extend it to do integers, floats, padding, etc.
If you are using ASP.Net and if you do not need the JSON result on the client (other than to do DOM-insertions) you could render the html on the server.
You could then take advantage of you programmning language functionality to build up the html.
Finally you return the HTML-string and append it to you DOM.
As an example you can create your DOM-elements like this:
var panel = new Panel();
var label = new Label { Text = data.contents };
panel.Controls.Add(label);
return panel.ToHtml(); //use stringwriter
This teqnique have proven very useful in several projects i have been involved in.
another option which maybe more maintainable, testable etc would be for your remote function itself (that your calling via ajax) to return the formatted html instead of the data. For example in MVC, e.g ASP.NET MVC, you can return a view and the script would take this data as is without having to manipulate it.
[AcceptVerbs((HttpVerbs.Get))]
public ActionResult GetView(string param1)
{
var model = new ModelXXX;
...
...
return View("ViewName", model);
}

templates vs DOM creation - highly dynamic interface

Building a browsergame I came from PHP to JavaScript, which I now also want to use at the server side.
As I'm going to require Users to have JavaScript either way, I'm going to take extensive use of it. I want to use in in a object-oriented way though.
Considering MVC, Models will be used on both client and server side. Views are only used at the client side.
The interface is split into multiple parts: a main sidemenu, main content and some widgets. I'll take the part I've already done as example:
The menu is split into three categories with multiple entries. Each entry is a link with an attached action (like switching the content).
// menuview:
var self = new View();
var generalMenu = new MenuCategory('generalmenu')
.addEntry(new MenuEntry('overview', new Action()))
.addEntry(new MenuEntry('buildings'))
.addEntry(new MenuEntry('resources'))
// [..more categories..]
self.toData = function() {
return {
id: this.id,
cat: [generalMenu.toData(), infosMenu.toData(), userMenu.toData()]
};
};
At the moment View is a compositum with a toData() method to create data for the template parser(selfmade, simple but supporting iteration). And the actions get attached after creation. I use jQuery as framework:
self.show = function(callback) {
$tpl(this.tpl).parse(this.toData()).lang('main').toHTML(function(html) {
var el = $(html);
el.find('a').click(function (e) {
MenuEntry.actionHandler.execAction(e.target.id);
return false;
});
el.appendTo('#'+self.target);
callback && callback();
});
return this;
};
I have declared an actionhandler to avoid iterating over the links.
I'm not feeling well with this solution, it's not flexible enough. I'd like to treat a view like a real compositum, not with a lot of strange dependencies. Also, I have to reparse the whole View if I change a part. Well, in this example this is not obvious, because the menu wont change while runningtime, but other parts of the interface will.
Now, to finally get to my question: Is there a better solution?
Like having dom references spread over the view, each menuentry having it's own reference and directly attached action? If I'm not using templates anymore, what kind of flexiblity am I losing?
I decided to go without template parser. Each view stores it's node and is able to manipulate it directly if it gets informed to update the data.

How does alfresco's javascript( not webscript) mechanism

When I play with alfresco share, I found it is difficult to track the UI and javascript. you can only see some class name in the HTML tags, But you are difficult to know how are they constructed, And When, where and how can these scattered HTML code can render such a fancy page.
Can someone help me ? Please offer several example and explain how they work!
Thanks in advance!
Here is some example that will hopefully help you (it's also available on Wiki). Most of the magic happens in JavaScript (although the layout is set in html partly too).
Let's say you want to build a dashlet. You have several files in the layout like this:
Server side components here:
$TOMCAT_HOME/share/WEB-INF/classes/alfresco/site-webscripts/org/alfresco/components/dashlets/...
and client-side scripts are in
$TOMCAT_HOME/share/components/dashlets...
So - in the server side, there is a dashlet.get.desc.xml - file that defines the URL and describes the webscript/dashlet.
There is also a dashlet.get.head.ftl file - this is where you can put a <script src="..."> tags and these will be included in the <head> component of the complete page.
And finally there is a dashlet.get.html.ftl file that has the <script type="text/javascript"> tag which usually initializes your JS, usually like new Alfresco.MyDashlet().setOptions({...});
Now, there's the client side. You have, like I said, a client-side script in /share/components/dashlets/my-dashlet.js (or my-dashlet-min.js). That script usually contains a self-executing anonymous function that defines your Alfresco.MyDashlet object, something like this:
(function()
{
Alfresco.MyDashlet = function(htmlid) {
// usually extending Alfresco.component.Base or something.
// here, you also often declare array of YUI components you'll need,
// like button, datatable etc
Alfresco.MyDashlet.superclass.constructor.call(...);
// and some extra init code, like binding a custom event from another component
YAHOO.Bubbling.on('someEvent', this.someMethod, this);
}
// then in the end, there is the extending of Alfresco.component.Base
// which has basic Alfresco methods, like setOptions(), msg() etc
// and adding new params and scripts to it.
YAHOO.extend(Alfresco.MyDashlet, Alfresco.component.Base,
// extending object holding variables and methods of the new class,
// setting defaults etc
{
options: {
siteId: null,
someotherParam: false
},
// you can override onComponentsLoaded method here, which fires when YUI components you requested are loaded
// you get the htmlid as parameter. this is usefull, because you
// can also use ${args.htmlid} in the *html.ftl file to name the
// html elements, like <input id="${args.htmlid}-my-input"> and
// bind buttons to it,
// like this.myButton =
// so finally your method:
onComponentsLoaded: function MyDaslet_onComponentsLoaded(id) {
// you can, for example, render a YUI button here.
this.myButton = Alfresco.util.createYUIButton(this, "my-input", this.onButtonClick, extraParamsObj, "extra-string");
// find more about button by opening /share/js/alfresco.js and look for createYUIButton()
},
// finally, there is a "onReady" method that is called when your dashlet is fully loaded, here you can bind additional stuff.
onReady: function MyDashlet_onReady(id) {
// do stuff here, like load some Ajax resource:
Alfresco.util.Ajax.request({
url: 'url-to-call',
method: 'get', // can be post, put, delete
successCallback: { // success handler
fn: this.successHandler, // some method that will be called on success
scope: this,
obj: { myCustomParam: true}
},
successMessage: "Success message",
failureCallback: {
fn: this.failureHandler // like retrying
}
});
}
// after this there are your custom methods and stuff
// like the success and failure handlers and methods
// you bound events to with Bubbling library
myMethod: function (params) {
// code here
},
successHandler: function MyDAshlet_successHandler(response) {
// here is the object you got back from the ajax call you called
Alfresco.logger.debug(response);
}
}); // end of YAHOO.extend
}
So now you have it. If you go through the alfresco.js file, you'll find out about stuff you can use, like Alfresco.util.Ajax, createYUIButton, createYUIPanel, createYUIeverythingElse etc. You can also learn a lot by trying to play with, say, my-sites or my-tasks dashlets, they're not that complicated.
And Alfresco will put your html.ftl part in the page body, your .head.ftl part in the page head and the end user loads a page which:
loads the html part
loads the javascript and executes it
javascript then takes over, loading other components and doing stuff
Try to get that, and you'll be able to get the other more complicated stuff. (maybe :))
You should try firebug for stepping through your client side code.
Alfresco includes a bunch of files that are all pulled together on the server side to serve each "page".
I highly recommend Alfresco Developer Guide by Jeff Potts (you can buy it and view it online instantly).
James Raddock
DOOR3 Inc.

Categories