I'm trying to set up the base architecture for a Phonegap project using jQueryMobile and Backbone.js
We will be targeting multiple platforms and plan on expanding in the future.
I want my Backbone code to be completely ignorant of the types of HTML elements associated with the Views since we have no idea what the views will look like across the platforms.
A simple example would be that on one platform a List of items might be made from a UL and LI elements, but on another for some reason we might want to switch it to a DIV and P elements.
At the moment, if I don't specify a tagName, it defaults to a DIV.
I want to be able to provide a template like this on one page:
<script id="attachmentTemplate" type="text/template">
<li><%= title %></li>
</script>
and on another provide:
<script id="attachmentTemplate" type="text/template">
<p><%= title %></p>
</script>
I can't do that without specifying a tagName (as I said, it defaults to DIV).
Does anyone have any advice or methods to do this? The key is that I don't want the Javascript to be aware of the type of HTML element, I want the HTML to define that.
You can specify a tagName property in your view with a function, so it will be dynamic.
...
tagName: function(){
return this.model.get('tagName');
},
...
Checkout this fiddle http://jsfiddle.net/b7mR6/
Though I wouldn't recommend this architecture you can override your render method:
render: function()
{
// unbind all handlers since delegateEvents was called in the constructor
this.undelegateEvents();
// set this.el to the domElement from the templates
this.el = this.template(this.model.toJSON()); // or however you get to your template
// create the shorthand dom reference that setElement() creates
this.$el = $(this.el);
// re-delegate the events
this.delegateEvents();
}
what that means is that you don't need to worry about your tagName as you defined it in your template.
edit: looking through the Backbone code, this is exactly what setElement does, so in theory you could try
render: function()
{
var tpl = this.template(this.model.toJSON());
this.setElement(tpl, true);
return this;
}
As usual, I search and search and find no answer, then I ask a question and figure it out on my own.
I discovered that if I make my Template like this:
<script id="attachmentTemplate" tagname="li" type="text/template">
<%= title %>
</script>
Then in my js code, when instantiating my View I can do this:
var attachmentView = new AttachmentListItemView({ tagName: $('#attachmentTemplate').attr('tagname'), model: item });
And it works perfectly.
No idea if that will be valid HTML or cause any problems down the road though.
Related
I am working on a web project. (Absolutely no previous experience).
This project requires retrieving lot of content from the server dynamically(Like on the click of button).
Now, the problem is, I have to display the content in some neat, formatted way. Like, collapsible lists, hyperlinks etc. I am currently using JavaScript for this purpose. Like this:
li = document.createElement("li");
li.innerHTML = "some_content";
I also need to add appropriate classes to those dynamically created elements in JavaScript as per the requirements of Bootstrap.
But the code really looks very messy now. Are there any alternative solutions for avoiding all the dynamic creation of elements in JS, formatting etc.?
Seems like a perfect scenario for a knockoutJS solution. I am just posting a sample code here from their live example.
you just create the template like this -
<p>First name: <input data-bind="value: firstName" /></p>
<p>Last name: <input data-bind="value: lastName" /></p>
<h2>Hello, <span data-bind="text: fullName"> </span>!</h2>
(notice the bindings in data-bind)
and create the view model and then apply it -
// Here's my data model
var ViewModel = function(first, last) {
this.firstName = ko.observable(first);
this.lastName = ko.observable(last);
this.fullName = ko.pureComputed(function() {
// Knockout tracks dependencies automatically. It knows that fullName depends on firstName and lastName, because these get called when evaluating fullName.
return this.firstName() + " " + this.lastName();
}, this);
};
ko.applyBindings(new ViewModel("Planet", "Earth")); // This makes Knockout get to work
and you are good to go with this output, only a matter of minutes.
I created a Survey Builder page with it, the most important part is knockout is fast and also supports all kinds of js libraries to work with like jQuery, AngularJS, etc. You will never need to worry about rendering ui, just get/set with data.
Try it out here - http://jsfiddle.net/rniemeyer/LkqTU/
You can try some front end framework. eg: AngularJS (As mentioned in previous comment)
It has built in template engine. (As mentioned in comment ) so your code would like following if you use angular
<ul>
<li ng-repeat="user in users">{{user.name}}</li>
</ul>
and your json might be
{
"users":[
{
"name":"jack",
"age":18
},
{
"name":"jimmy",
"age":19
}
]
}
the html user see will be
<ul>
<li>jack</li>
<li>jimmy</li>
</ul>
I didn't write angular's controller code,so the above code is just for demonstration, you can take a look at its doc and tutorials.
Angular is a MV* front end framework, it is really useful when you have a lot of
data to display in the front end.(but too many data will cause performance issue because it use two-way data-binding, try bindonce)
If you don't want a framework, you can try mustache
You can use a templating solution like underscore templates, Handlebars, or (the best) JADE. Each of these can be used with a larger framework like angular or ember, but also work as standalone templating solutions.
Since you're using bootstrap, you're also using jQuery.
With jQuery, you can add elements, update their content and add classes like this:
var $li = $("<li>") // create the li element
.text("My Text") // add a text node
.addClass("class1 class2 class3") // add some classes
.appendTo($("ul#myList")); // insert into your ul
the following is also allowed if you prefer a one-liner:
var $li = $("<li class='class1 class2 class3'>My Text</li>")
.appendTo($("ul#myList"));
both of the above take this:
<ul id="myList"></ul>
and turn it into this:
<ul id="myList"><li class='class1 class2 class3'>My Text</li></ul>
Edit:
It's been a while, but I found Gatsy pretty easy to get up and running with. I've used it for a couple of projects.
Angular is another option, depending on how complex the site is (supported by google).
Ember is also a great option.
Depending on your skill sets, and the type of site this is, PHP could be an easy alternative for you as well.
I'm trying to use the new components system in knockout 3.2.0.
There isn't much documentation at the moment, but this does work.
ko.components.register('price-input', {
template: '<span>price-input</span>'
})
However the template binding allows you to specify a template name that already exists in the DOM, such as:
<script type="text/html" id="price_input">
<span>price-input</span>
</script>
Then you could do this:
<div data-bind="template: {name: 'price_input'}"></div>
So I tried this:
ko.components.register('price-input', {
template: {name: 'price_input'}
})
but it doesnt work. Is there a way to use named templates with the new components, or must they be inline or loaded with AMD.
Thanks
Edit: After RP Niemeyer's answer, for clarification here is the template I tried his answer with:
<script type="text/html" id="ifx_price_input">
<h4>PRICE INPUT <span data-bind="text: value"></span></h4>
</script>
Here is the component code:
ko.components.register('price-input', {
template: {element: 'ifx_price_input'}
})
It does load the template, but treats it as an escaped string.
Ideas?
You can pass an element property that is either an element itself or a string that is the id of the element like:
template: { element: 'myTmpl' }
In v3.2.0 beta, this case wasn't handled well, hence the hackery needed by InternalFX.
This will be fixed in v3.2.0 final. It will work as you expect - simply reference a script, template, or textarea element, and its logical contents will be intepreted as template nodes.
In case you're interested, the commit that fixes and tests this is here: https://github.com/knockout/knockout/pull/1454
Finally solved this with some hackery...I hope this gets answered better by the knockout devs.
This works. Basically I just load the template text manually and pass it to the register function. So it works as if it was inline.
ko.components.register('price-input', {
template: $('#ifx_price_input').html()
})
I have a working solution in regard to rendering layouts with views in regions in a Marionette application I'm working on, but something doesn't feel right about it. Do you have to append anything directly to the DOM?
Here is my method in the controller:
//Grab the main Layout
var layout = new APP.Views.LayoutView();
//Render that layout
layout.render();
//Make the model
var simpleModel = new APP.Models.simpleModel({
"field1" : "foo",
"field2" : "bar"
});
layout.header.show(new APP.Views.subView({model: simpleModel}));
$('body').html(layout.el);
It's the last part that feels unnatural to me. This is primarily because if I move 'layout.header.show' to after the .html(), then it doesn't render properly. What's the point of regions if they aren't dynamically changable after pushing it to the DOM?
Here is my layout:
APP.Views.LayoutView = Backbone.Marionette.Layout.extend({
template: "#layoutTemplate",
className : 'wrapper',
regions: {
header: "#header",
footer: "#footer"
}
});
and here is the sub view:
APP.Views.subView = Backbone.Marionette.ItemView.extend({
template : '#headerTemplate',
className: 'container'
});
As I said, this works, but it feels like I'm not using regions properly. Is there a better, more concise way to do this that will allow you access to regions after you rend the layout to the DOM?
In the Marionette documentation there seems to be no mention of using .html() to get things on the page -- I'm wondering if this is left out because it's not needed or that it's assumed.
Can anyone help?
Okay, so it seems like this can be circumvented by creating a 'region' in your application, then using .show() to show the layouts inside of it.
Here is a link to a fiddle I found on SO that helped: http://jsfiddle.net/TDqkj/6/
as well as to another question: Understanding layouts in Marionette for Backbone.js
The fiddle in particular has this code:
App = new Backbone.Marionette.Application();
App.addRegions({
'nav': '#nav',
'content': '#content'
});
Which the programmer than uses to add layouts to those regions -- meaning you never have to append to the DOM.
If anyone else has a more elegant solution, please post!
I'm just getting my feet wet with Ember.js, and I've hit something that I'm sure I'm not understanding.
I've got a selected object controller. It has content, which is an Ember.Object, which is the currently selected model. The model has a property (isDirty), and basically I'd like my save button on my form to be enabled only when the object is dirty and needs to be saved.
I've managed to bind up the form just fine, but the isEnabledBinding property on the save button is either not doing anything or I'm not hooking up the binding properly.
I've prepared a jsfiddle demonstrating my basic set up.
http://jsfiddle.net/blargity/fqc73/1/
How do I get the button to be enabled only when isDirty is true? The bindings should also work if the content property on the selected object controller changes.
I found a way to do this without using the now-deprecated Ember.Button.
In the handlebars template:
<button {{action "save" target="controller"}} {{bindAttr disabled="view.isNotDirty"}}>Save</button>
In the view:
isNotDirty: function(){
return !this.get('controller.content.isDirty')
}.property('controller.content.isDirty').cacheable()
(With the version of Ember I have, Ember.Binding.not does not exist. Maybe I need to update, but the docs don't show it either so perhaps it was actually removed.)
The problem is that there is no isEnabled property on Ember.Button. You need to bind to the disabled property.
One possibility is to create a custom Ember.Button which handles this for you, see http://jsfiddle.net/LabpW/.
Handlebars:
{{#view App.SaveModelButton modelBinding="model"}}Save{{/view}}
JavaScript:
App.SaveModelButton = Ember.Button.extend({
disabledBinding: Ember.Binding.not('model.isDirty')
});
The used Ember.Binding.not is just a shortcut for writing your own computed property, which would look like this:
App.SaveModelButton = Ember.Button.extend({
disabled: function() {
return !Ember.getPath(this, 'model.isDirty');
}.property('model.isDirty').cacheable()
});
I also refactored your code a bit:
You mixed create and extend: use create for instances and extend for classes. There is a good blog post about this
It's kind of a convention to use lowerCase for instances and UpperCase for classes, so it should be App.controller instead of App.Controller
When ASP.NET controls are rendered their ids sometimes change, like if they are in a naming container. Button1 may actually have an id of ctl00_ContentMain_Button1 when it is rendered, for example.
I know that you can write your JavaScript as strings in your .cs file, get the control's clientID and inject the script into your page using clientscript, but is there a way that you can reference a control directly from JavaScript using ASP.NET Ajax?
I have found that writing a function to parse the dom recursively and find a control that CONTAINS the id that I want is unreliable, so I was looking for a best practice rather than a work-around.
This post by Dave Ward might have what you're looking for:
http://encosia.com/2007/08/08/robust-aspnet-control-referencing-in-javascript/
Excerpt from article:
Indeed there is. The better solution
is to use inline ASP.NET code to
inject the control’s ClientID
property:
$get('<%= TextBox1.ClientID %>')
Now the correct client element ID is
referenced, regardless of the
structure of the page and the nesting
level of the control. In my opinion,
the very slight performance cost of
this method is well worth it to make
your client scripting more resilient
to change.
And some sample code by Dave from the comment thread of that post:
<script>
alert('TextBox1 has a value of: ' + $get('<%= TextBox1.ClientID %>').value);
</script>
The comment thread to the article I linked above has some good discussion as well.
You can change to ClientIDMode property of the control to 'Static' that will result the same ID that you give the control in the .NET code.
<asp:TextBox ID="TextBox1" ClientIDMode="Static" runat="server"></asp:TextBox>
will result:
<input name="ctl00$MainContent$TextBox1" type="text" id="TextBox1">
so you have the same ID.
Couple of thoughts on this:
1) I've had a lot of luck getting elements by css class instead of id because asp.net ids are not reliable as you stated. I use this function and it performs reasonably well:
function getElementsByClass(searchClass,node,tag) {
var classElements = new Array();
if ( node == null )
{
node = document;
}
if ( tag == null )
{
tag = '*';
}
var els = node.getElementsByTagName(tag);
var elsLen = els.length;
var pattern = new RegExp("(^|\\s)"+searchClass+"(\\s|$)");
for (i = 0, j = 0; i < elsLen; i++)
{
if ( pattern.test(els[i].className) )
{
classElements[j] = els[i];
j++;
}
}
return classElements;
}
2) jQuery helps here alot. Using jQuery you can reliably get elements where the id ends with a certain string. While this is not "the" reason to use jQuery it's definitely a plus.
3) This will be fixed in asp.net 4.0 so hang in there :-) http://weblogs.asp.net/asptest/archive/2009/01/06/asp-net-4-0-clientid-overview.aspx
I prefer data bound tags in the markup document.getElementById('<%#TextBox1.ClientID %>').value, over the use of the server side tag implementation <% = TextBox1.ClientID %>.
Server side tags prohibit you from adding controls to the dom in the code behind. This need commonly arises as you build out your application and the databound approach may save you from major rewrites.
When using server side tags also know as 'code blocks' performing this common operation
this.Form.Controls.Add(myContorl);
generates this error at run time:
The Controls collection cannot be modified because the control
contains code blocks (i.e. <% ... %>).
Unfortunately this often only become inherently obvious after you have built out your web site.
When implementing data bound control '<%#TextBox1.ClientID %>' resolve the value of control properties referenced in the markup, in the appropriate place such as the end of Page_Load data bind like this:
Page.DataBind()
Keep in mind Page.DataBind() causes child controls on the page to also DataBind, this may be an unwanted side effect if the page handles the data binding of certain child controls separately. If this is the case, data binding can be performed on the individual control like this:
TextBox1.DataBind()
An applications evolution eventually leads to some sort of base site wide functionality where you may want to add base controls, once you've peppered you website application with server side tags replacing them with databinds becomes problematic, especially when pages have been coded to handle databinding on their own.
I don't think there's a single "best practice" for doing this. There's plenty of different pretty good practices. Here's ours:
Every control which has client-side functionality renders a script block inline, directly below the markup for the control:
<span id="something_crazy_long">
control markup
</span>
<script type="text/javascript">new CustomControl('something_crazy_long');</script>
Each control has an accompanying JS like:
var CustomControl = function(id) {
this.control = document.getElementByID(id);
this.init();
};
CustomControl.prototype.init = function() {
//do stuff to wire up relevant events
};
In the codebehind, we do something like:
class CustomControl : Control
override void Render(HtmlTextWriter writer)
{
writer.WriteBeginTag("span");
writer.WriteAttribute("id", this.ClientID);
writer.Write(HtmlTextWriter.TagRightChar);
//write control markup
writer.WriteEndTag("span");
writer.WriteBeginTag("script");
writer.WriteAttribute("type", "text/javascript");
writer.Write(HtmlTextWriter.TagRightChar);
writer.Write(
string.Format("new CustomControl('{0}');", this.ClientID)
);
writer.WriteEndTag("script");
}
I do something similar to Rex M except to avoid multiple script tags I use a function in my page base class to register the controls I am going to use clientside, then spit them out to html inside 1 script tag.
You could even subclass your controls to automatically register with the function or use a boolean property to set whether you are going to use them clientside.
For 'ctl00_ContentMain_Button1' - In asp.net when page renders in the browser, first part remains same 'ctl00'. Second part is ID of ContentPlaceHolder used 'ContentMain'. Third is ID of a control 'Button1'
I liked this http://codedotnets.blogspot.in/2012/01/how-get-id-server-control-javascript.html
Oh, and I also found this, in case anyone else is having this problem.
Use a custom jQuery selector for asp.net controls:
http://john-sheehan.com/blog/custom-jquery-selector-for-aspnet-webforms/
You can get the ID by using document.getElementById method as well.