Separating template logic from Backbone.View - model-view-controller

I just started learning Backbone.js, and have been working on (what else) a simple to-do application. In this app, I want to display my to-do items inside of <ul id="unfinished-taks"></ul> with each task as a <li> element. So far, so simple.
According to the tutorials I have read, I should create a View with the following:
// todo.js
window.TodoView = Backbone.View.extend({
tagName: 'li',
className: 'task',
// etc...
});
This works fine, but it seems like bad practice to define the HTML markup structure of my to-do item inside of my Javascript code. I'd much rather define the markup entirely in a template:
// todo.js
window.TodoView = Backbone.View.extend({
template: _.template($("#template-task").html()),
// etc...
});
<!-- todo.html -->
<script type="text/template" id="template-task">
<li class="task <%= done ? 'done' : 'notdone' %>"><%= text %></li>
</script>
However, if I do it that way Backbone.js defaults to using tagName: 'div' and wraps all my to-do items in useless <div> tags. Is there a way to have the HTMl markup entirely contained within my template without adding unsemantic <div> tags around every view element?

If you are only planning to render the view once, you can set the el property of the view manually in .initialize():
// todo.js
window.TodoView = Backbone.View.extend({
template: _.template($("#template-task").html()),
initialize: function() {
this.el = $(this.template(this.model.toJSON())).get(0);
},
// etc
});
There are some caveats here, though:
Backbone expects the el property to be a single element. I'm not sure what will happen if your template has multiple elements at the root, but it probably won't be what you expect.
Re-rendering is difficult here, because re-rendering the template gives you a whole new DOM element, and you can't use $(this.el).html() to update the existing element. So you have to somehow stick the new element into the spot of the old element, which isn't easy, and probably involves logic you don't want in .render().
These aren't necessarily show-stoppers if your .render() function doesn't need to use the template again (e.g. maybe you change the class and the text manually, with jQuery), or if you don't need to re-render. But it's going to be a pain if you're expecting to use Backbone's standard "re-render the template" approach for updating the view when the model changes.

Related

SharepointFramework - how to set the actual webpart code as initial value in PropertyFieldCodeEditor

Hello i am using this custom property pane control called PropertyFieldCodeEditor. what i want is to display the actual webpart code as the initial value of the code editor, then after i click save, the changes will be reflected on the webpart..
this is the code of PropertyFieldCodeEditor
PropertyFieldCodeEditor('htmlCode', {
label: 'Edit Code',
panelTitle: 'Edit Code',
initialValue: "",
onPropertyChange: this.onPropertyPaneFieldChanged,
properties: this.properties,
disabled: false,
key: 'codeEditorFieldId',
language: PropertyFieldCodeEditorLanguages.HTML
})
i tried to put this.domElement on initialvalue but it only accept string, also i cand find a way to convert this.domelement to string..
also what should i put inside
protected onPropertyPaneFieldChanged(path: string, newValue: string) {}
For initialValue, you should be able to use this.domElement.innerHTML or this.domElement.outerHTML. Both are strings representing the contents of domElement (note, domElement is really just an HTMLElement).
outerHTML will include everything, including one extra div layer on the outside:
<div style="width: 100%;">
<div class="helloWorld_d3285de8">
...
</div>
</div>
innerHTML is only the inside contents of that div:
<div class="helloWorld_d3285de8">
...
</div>
You'll probably want innerHTML, since that's what's initially used in the render method.
Once you set the initialValue, you would have accomplished copying your web part code into the PropertyFieldCodeEditor. Now you would need to get the PropertyFieldCodeEditor contents (which is stored in your property htmlCode) assigned back into this.domElement.innerHTML.
Unfortunately, in onPropertyPaneFieldChanged, this points to the PropertyFieldCodeEditor, not to the web part class anymore. You may or may not be able to do it here - I didn't look too deeply into it.
The easiest solution I figured was, in render, to assign this.domElement.innerHTML like so:
public render(): void {
this.domElement.innerHTML = this.properties.htmlCode || `
<div class="${styles.helloWorld}">
...
</div>`;
}
This way, the web part will initially render whatever comes after the ||. But as soon as you save a change to the PropertyFieldCodeEditor, it will start rendering the htmlCode instead. This only works because initially htmlCode is undefined. (Note it won't work exactly like this if you assign something truthy to it via your web part's preconfiguredEntries - you would have to write some further checks. The principle is the same, though.)

Binding Vue.js to all instances of an element, without(?) using Components

Today I'm learning Vue.js, and I have a few ideas of where it might be really useful in a new project that's an off-shoot of an existing, live project.
I like the idea of trying to replace some of my existing functionality with Vue, and I see that Components may be quite handy as quite a lot of functionality is re-used (e.g. Postcode lookups).
Once of the pieces of functionality I've used for an age is for invalid form elements - currently in jQuery when a form input or textarea is blurred I add a class of form__blurred, and that is coupled with some Sass such as:
.form__blurred {
&:not(:focus):invalid {
border-color:$danger;
}
}
This is to avoid styling all required inputs as errors immediately on page load.
I'm totally fine with doing this in jQuery, but I figured maybe it could be done in Vue.
I have an idea of how I might do it with components thanks to the laracasts series, but my form inputs are all rendered by Blade based on data received from Laravel and it doesn't seem like a neat solution to have some of the inputs rendered in Javascript, for a number of reasons (no JS, confusion about where to find input templates, etc).
I figured something like the following simplified example would be handy
<input type="text" class="form__text" v-on:blur="blurred" v-bind:class="{ form__blurred : isBlurred }" />
<script>
var form = new Vue({
el : '.form__input',
data : {
isBlurred : false
},
methods : {
blurred : function() {
this.isBlurred = true;
}
}
});
</script>
That actually works great but, as expected, it seems like using a class selector only selects the first instance, and even if it didn't, I'm guessing changing the properties would apply to all elements, not each one individually.
So the question is - is this possible with pre-rendered HTML, in a way that's smarter than just looping through a selector?
If it is, is there a way to create the Vue on a .form element and apply this function to both .form__input and .form__textarea?
Or, as is probably the case, is this just not a good use-case for Vue (since this is actually a lot more code than the jQuery solution).
Sounds like a great use case for a Custom Directive.
Vue allows you to register your own custom directives. Note that in Vue 2.0, the primary form of code reuse and abstraction is components - however there may be cases where you just need some low-level DOM access on plain elements, and this is where custom directives would still be useful.
<div id="app">
<input type="text" name="myforminput" v-my-directive>
</div>
<script>
Vue.directive('my-directive', {
bind: function (el) {
el.onblur = function () {
el.classList.add('form__blurred');
}
}
});
var app = new Vue({
el: '#app'
});
</script>
You can also add the directive locally to a parent component, if it makes sense for your application.

Understanding Ember Views

I'm looking for some clarification on views in Ember.js
Coming from a rails background and I'm trying to ignore any preconceptions. From what I understand of the ember framework there are 5 components:
Routes: This is where we define the state of the application. The state is reflected in the URL. We can also define data loading here. Route classes are defined and on startup ember creates route objects which last for the duration of the application.
Models: This is where object data is defined. Can also define computed properties. A model object is created for each json object returned from the server.
Controllers: This mediates interactions between the models and templates/views. Controller classes are defined and on startup ember creates controller objects which last for the duration of the application. There is only ever a single instance of each controller class.
Templates: These describe the generated markup.
Views: These are specific templates or dom elements relating to a model. These are used to define interface events and send them to the controller for handling. Not sure when to create these.
As an example lets say I have a EventsController that has data loaded on the applicationRoute:
ScheduleApp.EventsController = Ember.ArrayController.extend();
ScheduleApp.ApplicationRoute = Ember.Route.extend({
setupController: function() {
this.controllerFor('events').set('content', ScheduleApp.Event.find());
}
});
Now in my template instead of iterating over each and displaying the information I want to iterate over each and create an associated view so I can add interactions to each event. I presume I would need to create a new view for each event and have it display in my template. However, I'm not sure where I create these views. Do I define a view class and then ember will create a new view object each time I call it using the view helper? Eventually I would like to use the appendTo on the view to inject my events to different places in the dom. Where would this be defined?
I've tried reading over the ember.js guide for views but it describes the context of a creating a single view. I think I want to make many views for each event and then dynamically interact with those objects.
Up to now ember has been outrageously clever so I would assume there is a built in method for generating these views. After all, most user interfaces are full of lists that require interactions. The problem is the list I'm trying to make I then want to spread over the dom depending on its attributes.
As per your code, App.EventsController has a list of events, now let us say we want the UI to have a list of events displayed and for each event say we want the view to have a delete button which deletes the event when the user clicks
One way to accomplish is by using Ember.CollectionView, the collection view as the name suggests is tailored for these sort of requirements, in many Ember examples the usage of view is not defined because ember auto-generates it for you but in some cases we might need to explicitly define a view to meed our requirements
App.EventsView = Ember.CollectionView.extend({
// It needs a list of data to iterate upon
// We are binding it to the controllers content data which
// is a list of events
contentBinding: "controller.content",
appendSpan: function(){
view = Ember.View.create({tagName: 'span'});
this.get("childViews").forEach(function(child){
view.appendTo(child);
});
},
// Now we need to also define a view template that will be
// generated for all the elements in the content array
// This could in turn be another collection view if required
// I am going to keep it simple for now
itemViewClass: Ember.View.extend({
templateName: "event",
deleteEvent: function(){
// Implement Delete
this.get("content").deleteRecord();
},
notifyUser: function(){
// The record doesn't get deleted as soon as user clicks, the request goes to
// server and server deletes the record and sends back the response to the
// client, Hence I'm using an observer here on isDeleted property of the record
if(this.get('content.isDeleted')){
alert("Record deleted Successfully");
}
}.observes('content.isDeleted')
})
})
Important Note Inside the CollectionView definition this.get("content") refers to the array of events, while in itemViewClass this.get("content") refers to the single event object
//Defining the template
//Assuming the event model has a name property
<script type="text/x-handlebars" data-template-name="event">
Name: {{view.content.name}}
<a {{action deleteEvent target="view"}} href="#">Delete Event</a>
</script>
Now when you hit your application_url/events
you'll a list of events each event has a delete button, I hope this clears some concepts
For more information about CollectionView
Update as per the comment:
If you want to append another view to each child view, you can do so by editing the template of itemViewClass as follows
<script type="text/x-handlebars" data-template-name="event">
Name: {{view.content.name}}
<a {{action deleteEvent target="view"}} href="#">Delete Event</a>
{{ view App.SomeOtherView }}
</script>
it can be a partial too as follows
<script type="text/x-handlebars" data-template-name="event">
Name: {{view.content.name}}
<a {{action deleteEvent target="view"}} href="#">Delete Event</a>
{{ partial "somePartial" }}
</script>
or if you want to do it programmatically say, you click a button in the EventsView template and on click all the childs view must have a span tag appended to it(I am very bad at giving examples)
//This is the template for App.EventsController,
//template-name = "events" => view is App.EventsView and controller App.EventsController
<script type="text/x-handlebars" data-template-name="events">
<a {{action appendSpan target="view"}} href="#"> Append </a>
</script>
appendSpan is defined in the CollectionView

Ember.js: How do I access a specific item in a CollectionView?

First off I want to say that I really like ember.js. I have tried both Knockout and Angular but found them a bit to obtrusive and everything had to be done their way. I feel like ember allows me a bit more freedom to structure things how you see fit. With that said I have a couple of questions.
1. I would like to do something like the following which obviously doesn't work:
<h3>{{ content.name }}</h3>
Instead I would have to create a binding:
<a {{ bindAttr href="url" }}><h3>{{ content.name }}</h3></a>
How do i create the url path in the view? I could easily create a computed property called url on the model but that feels horrible and not very MVC. Do I have to create a new view for the element or register a helper which feels a bit cumbersome?
Here's the complete code:
App = Ember.Application.create();
var sampleData = [ Ember.Object.create({ id: '123456789', name: 'John' }), Ember.Object.create({ id: '987654321', name: 'Anne' }) ]
App.itemController = Ember.ArrayController.create({
content: sampleData,
removeItem: function(item) {
this.content.removeObject(item);
}
});
App.ItemListView = Ember.View.extend({
itemDetailView: Ember.CollectionView.extend({
contentBinding: 'App.itemController',
itemViewClass: Ember.View.extend({
tagName: 'li',
url: '' // HOW TO GET '/item/123456789'
deleteButton: Ember.Button.extend({
click: function(event) {
var item = this.get('content');
App.itemController.removeItem(item);
}
})
})
})
});
<script type="text/x-handlebars">
{{#view App.ItemListView}}
<ul id="item-list">
{{#collection itemDetailView}}
<div class="item">
<a {{ bindAttr href="url" }}><h3>{{ content.name }}</h3></a>
{{#view deleteButton class="btn" contentBinding="content"}}Delete{{/view}}
</div>
{{/collection}}
</ul>
{{/view}}
</script>
2. I feel that the view "owns" the controller and not the other way around. Shouldn't the view be unaware of which controller it is hooked up to so you can reuse the view? I'm thinking about these to lines in the view:
contentBinding: 'App.itemController',
and
App.itemController.removeItem(item);
How do you structure this?
3. I realize everything is a work in progress and quite new with the name change and all but the documentation is quite unclear. The examples use the old namespace SC and there are lot of things missing on emberjs.com compared to the Sproutcore 2.0 guides, for example collections, arraycontrollers. I read somewhere here that collections will be phased out. Is that true and should I use #each instead?
Thanks for your help and for an awesome framework!
1.) If you want to use <a href="...">, you will need a computed property. It could be on your model or on a view. Another technique would be to use Ember.Button: {{#view Ember.Button tagName="a" target="..." action="..."}}...{{/view}}
2.) Typically you'll want to declare your controller binding in the template, rather than in the view. For example: {{#view App.ItemListView contentBinding="App.itemController"}}
3.) The #collection helper will likely be deprecated, so you should probably use an #each instead.

Reloading everything but one div on a web page

I'm trying to set up a basic web page, and it has a small music player on it (niftyPlayer). The people I'm doing this for want the player in the footer, and to continue playing through a song when the user navigates to a different part of the site.
Is there anyway I can do this without using frames? There are some tutorials around on changing part of a page using ajax and innerHTML, but I'm having trouble wrapping my head aroung getting everything BUT the music player to reload.
Thank you in advance,
--Adam
Wrap the content in a div, and wrap the player in a separate div. Load the content into the content div.
You'd have something like this:
<div id='content'>
</div>
<div id='player'>
</div>
If you're using a framework, this is easy: $('#content').html(newContent).
EDIT:
This syntax works with jQuery and ender.js. I prefer ender, but to each his own. I think MooTools is similar, but it's been a while since I used it.
Code for the ajax:
$.ajax({
'method': 'get',
'url': '/newContentUrl',
'success': function (data) {
// do something with the data here
}
});
You might need to declare what type of data you're expecting. I usually send json and then create the DOM elements in the browser.
EDIT:
You didn't mention your webserver/server-side scripting language, so I can't give any code examples for the server-side stuff. It's pretty simple most of time. You just need to decide on a format (again, I highly recommend JSON, as it's native to JS).
I suppose what you could do is have to div's.. one for your footer with the player in it and one with everything else; lets call it the 'container', both of course within your body. Then upon navigating in the site, just have the click reload the page's content within the container with a ajax call:
$('a').click(function(){
var page = $(this).attr('page');
// Using the href attribute will make the page reload, so just make a custom one named 'page'
$('#container').load(page);
});
HTML
<a page="page.php">Test</a>
The problem you then face though, is that you wouldnt really be reloading a page, so the URL also doesnt get update; but you can also fix this with some javascript, and use hashtags to load specific content in the container.
Use jQuery like this:
<script>
$("#generate").click(function(){
$("#content").load("script.php");
});
</script>
<div id="content">Content</div>
<input type="submit" id="generate" value="Generate!">
<div id="player">...player code...</div>
What you're looking for is called the 'single page interface' pattern. It's pretty common among sites like Facebook, where things like chat are required to be persistent across various pages. To be honest, it's kind of hard to program something like this yourself - so I would recommend standing on top of an existing framework that does some of the leg work for you. I've had success using backbone.js with this pattern:
http://andyet.net/blog/2010/oct/29/building-a-single-page-app-with-backbonejs-undersc/
You can reload desired DIVs via jQuery.ajax() and JSON:
For example:
index.php
<script type="text/javascript" src="jquery-1.4.2.js"></script>
<script type="text/javascript" src="ajax.js"></script>
<a href='one.php' class='ajax'>Page 1</a>
<a href='two.php' class='ajax'>Page 2</a>
<div id='player'>Player Code</div>
<div id='workspace'>workspace</div>
one.php
<?php
$arr = array ( "workspace" => "This is Page 1" );
echo json_encode($arr);
?>
two.php
<?php
$arr = array( 'workspace' => "This is Page 2" );
echo json_encode($arr);
?>
ajax.js
jQuery(document).ready(function(){
jQuery('.ajax').click(function(event) {
event.preventDefault();
// load the href attribute of the link that was clicked
jQuery.getJSON(this.href, function(snippets) {
for(var id in snippets) {
// updated to deal with any type of HTML
jQuery('#' + id).html(snippets[id]);
}
});
});
});

Resources