JS code below
Model
var EntryName = Backbone.Model.extend({
defaults : {
name : ""
},
});
Model Collection
var EntryNames = Backbone.Collection.extend({
model : EntryName,
initialize : function() {
}
});
ModelView
var EntryNameView = Backbone.View.extend({
tagName : 'li',
// Cache the template function for a single item.
entrynametpl : _.template('<li><a href="#" ></a></li>'),
// Re-render.
Render function
render : function() {
this.$el.html(this.entrynametpl(this.model.toJSON()));
return this;
},
});
ModelCollectionView
var EntryNamesView = Backbone.View.extend({
// tagName: "ul",
// className: "nav-search",
el : $('#entriestree'),
initialize : function() {
//this.template = _.template($('#entries-template').html());
_.bindAll(this, 'render');
},
Render function
render : function() {
var item, self = this;
//var template = $("#item-template");
this.collection.each(function(entry) {
item = new EntryNameView({
model : entry
});
self.$el.append(item.render().el);
});
console.log($(this.el));
return this;
}
});
Model Collection
Model Collection This is where and how i call render.
function onDeviceReady()
{
// console.log("Opening panel");
$("#nav-panel").panel( "open");
console.log("creating collection");
var donuts = new EntryNames();
donuts.reset([ {"name" : "Boston Cream"}, {"name" : "Lemon-Filled"}, {"name" : "Rusty Iron Shavings"}]);
console.log("created collection");
var donutCollectionView = new EntryNamesView({collection : donuts});
donutCollectionView.render();
$("#nav-panel" ).trigger( "updatelayout" );
}
Model Collection
The HTML code is below Model Collection
<body>
<div id="panel-fixed-page1" class="jqm-demos ui-responsive-panel"
data-url="panel-fixed-page1" data-role="page">
<div data-role="header" data-theme="f" data-position="fixed">
<div data-role="navbar" data-grid="d">
<ul>
<li><a class="ui-btn-active" href="#" data- theme="a">Entry</a></li>
<li>Addresses</li>
<li>Attachments</li>
<li>Delivery Collection</li>
</ul>
</div>
<!-- /navbar -->
Menu
</div>
<!-- /header -->
<div class="jqm-content" data-role="content">
</div>
<!-- /content -->
<div id="nav-panel" data-role="panel"
data-position-fixed="true">
<li><a href="#" data-rel="close" >INBOX</a></li>
<ul id="entriestree" class="nav-search" data-role="listview" data- theme="a">
</ul>
</div>
</div>
</body>
Model Collection
Model Collection
In your EntryNamesView.render, this is not pointing to the view, because it is inside each callback function scope. Try change it to use a self:
render : function() {
var item, self = this;
//var template = $("#item-template");
this.collection.each(function(entry) {
item = new EntryNameView({
model : entry
});
self.$el.append(item.render().el);
});
console.log($(this.el));
return this;
}
Your _.underscore template has no placeholder variables:
entrynametpl : _.template('<li><a href="#" ></a></li>') //renders empty li's
correct this to:
entrynametpl: _.template('<li><%= name %></li>')
okay i figured out the issue, I set the view's element "el" property outside of the constructor (the initialize function), so it was never getting set with the proper dom element because the dom element came after the script reference in the html file. once i moved the script import to the end of the page, voila life is good..silly mistakes cost you sooo much time!!!
Related
QUESTION
How can I use PropertiesService to store an array from index.html, send the array to code.gs, and return the array in index.html?
SPECIFIC CASE
In a Google Web App, I have a group of sortable lists (made using JQuery UI Sortable). I want to save the most recent order/position of each li. I'm attempting to have that order/position "persist" when the page is refreshed or closed.
EXAMPLE
If you see the default Sortable, you could change the order of the items. If you refreshed the page, or closed it and return, the items would be in their original order.
WHERE I'M HAVING TROUBLE
I am able to get the array to show up in the console, but I don't know how to get it back to code.gs. I think I am now, but I'm not sure. Beyond that, I don't know how to "read" that PropertiesService so that the array is returned to index.html. I'm not really sure what I'm doing so if someone could slow walk me it would be appreciated!
ALTERNATIVES
I also looked into writing directly to the spreadsheet where the values originate. I'm not really sure how to do that either. I made some attempts, and was able to get "undefined" as a value in a spreadsheet cell.
FULL CODE (note: the list items are formed using an array, so they will not show up here): https://jsfiddle.net/nateomardavis/Lmcjzho2/1/
PARTIAL CODE
code.gs
function doGet() {
return HtmlService.createHtmlOutputFromFile('index');
}
function webAppTest() {
getTeamArray();
}
function getTeamArray() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName('TEST');
var range = sheet.getRange(2, 1, 1000, 1);
var values = range.getValues();
var teamsArray = [];
for (var i = 0; i < values.length; ++i) {
teamsArray.push(values[i][0]);
}
var uniqueArray = [];
uniqueArray.push(teamsArray[0]);
for (var i in teamsArray) {
if ((uniqueArray[uniqueArray.length - 1] != teamsArray[i]) && (teamsArray[i] !== "")) {
uniqueArray.push(teamsArray[i]);
}
}
return uniqueArray;
}
function savePositions(myProperty, positions) {
PropertiesService.getScriptProperties().setProperty("myProperty", JSON.stringify(positions));
};
function getPositions() {
var returnedObj = PropertiesService.getScriptProperties()
};
index.html
<body>
<div id="myList" class="connectedSortable">MY LIST</div>
<table id=table1>
<div id="team1">
<p>TEAM 1</p>
<br>
<div id="group" v>SELECTED</div>
<ul id="team1s" name='team1s' class="connectedSortable"></ul>
<div id="group">ALTERNATE</div>
<ul id="team1a" name='team1a' class="connectedSortable"></ul>
</div>
</table>
<table id=table2>
<div id="team2">
<p>TEAM 2</p>
<br>
<div id="group" v>SELECTED</div>
<ul id="team2s" name='team2s' class="connectedSortable"></ul>
<div id="group">ALTERNATE</div>
<ul id="team2a" name='team2a' class="connectedSortable"></ul>
</div>
</table>
<table id=table3>
<div id="team3">
<p>TEAM 3</p>
<br>
<div id="group" v>SELECTED</div>
<ul id="team3s" name='team3s' class="connectedSortable"></ul>
<div id="group">ALTERNATE</div>
<ul id="team3a" name='team3a' class="connectedSortable"></ul>
</div>
</table>
<table id=table4>
<div id="team4">
<p>TEAM 4</p>
<br>
<div id="group" v>SELECTED</div>
<ul id="team4s" name='team4s' class="connectedSortable"></ul>
<div id="group">ALTERNATE</div>
<ul id="team4a" name='team4a' class="connectedSortable"></ul>
</div>
</table>
<script>
$(function() {
google.script.run.withSuccessHandler(buildOptionsList)
.getTeamArray();
});
function buildOptionsList(uniqueArray) {
var div = document.getElementById('myList');
for (var i = 0; i < uniqueArray.length; i++) {
var ul = document.createElement('ul');
var li = document.createElement('li');
var cLass = li.setAttribute('class', 'ui-state-default');
var iD = li.setAttribute('id', uniqueArray[i]);
li.appendChild(document.createTextNode(uniqueArray[i]));
div.appendChild(ul);
div.appendChild(li);
}
}
$(function() {
$("#myList, #team1s, #team1a, #team2s, #team2a, #team2s, #team3s, #team3a, #team4s, #team4a").sortable({
connectWith: ".connectedSortable",
update: function(event, ui) {
var changedList = this.id;
var order = $(this).sortable('toArray');
var positions = order.join(';');
console.log({
id: changedList,
positions: positions
});
//Instead of using JSON to save, can I use the spreadsheet itself to save the positions and then pull it from there as I did with "buildOptionsList" above?
function saveList() {
google.script.run.savePositions("myProperty", JSON.stringify(positions));
JSON.parse("myProperty");
}
}
})
});
$(function getPositions(event, ui) {
var changedList = this.id;
var order = $(this).sortable('toArray');
var positions = order.join(';');
console.log({
id: changedList,
positions: positions
});
});
</script>
</body>
</html>
It's also possible to just use the browser's localStorage client side.
localStorage.setItem('id', positions); //Store positions in users browser
localStorage.getItem('id'); //Retrieve the stored positions later
Notes:
For this to work, the url(document.domain of the iframe="*.googleusercontent.com") from which your script is deployed must remain constant. During my brief testing, it was constant even when changing from /dev to /exec of the parent(script.google.com) and even during version update. But there's no official reference.
This solution is better than properties service, if you have multiple users, as each one will have their own data stored in their own browsers and there are no server calls during each change.
Using google.script.run simple example:
<script>
function sendStringToServer() {
var string=$('#text1').val();
google.script.run
.withSuccessHandler(function(s){
alert(s);
})
.saveString(string);
}
</script>
Google Script:
function myFunction() {
PropertiesService.getScriptProperties().setProperty('MyString', string);
return "String was saved in Service";
}
Client to Server Communication
I read over 20 different articles and forum topics about that, tried different solutions but I didn't cope with it.
The following code doesn't work. I need someone's help...
LoginView.js
var LoginView = Backbone.View.extend({
//el: $('#page-login'),
initialize: function() {
_.bindAll(this, 'gotoLogin', 'render');
//this.render();
},
events: {
'click #button-login': 'gotoLogin'
},
gotoLogin : function(e){
e.preventDefault();
$('#signup-or-login').hide();
$('#login').show();
return true;
}
});
login.html
<div data-role="page" id="page-login">
<!-- SignUp or Login section-->
<div id="signup-or-login" data-theme="a">
<a data-role="button" data-theme="b" id="button-signup"> Sign Up </a>
<a data-role="button" data-theme="x" id="button-login"> Login </a>
</div>
<!-- Login section-->
<div id="login" data-theme="a">
<button data-theme="b"> Login </button>
<button data-theme="x"> Cancel </button>
</div>
</div>
The page is created in method of Backbone.Router extended class.
loadPage('login.html', new LoginView());
From what I understand, $.mobile.loadPage() grabs the desired html and attaches it to the DOM.
Currently, you're trying to set el after the View has been instantiated.
However, notice that Backbone.View attaches el and $el when it's instantiated:
var View = Backbone.View = function(options) {
...
this._ensureElement();
this.initialize.apply(this, arguments);
this.delegateEvents();
};
Also notice that View.setElement() sets $el by passing a selector or a jQuery objected to View.el:
setElement: function(element, delegate) {
if (this.$el) this.undelegateEvents();
this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
this.el = this.$el[0];
if (delegate !== false) this.delegateEvents();
return this;
}
Bottom line:
You need to set el (in your case with the provided jQuery object) while instantiating it:
// Where `view` is a reference to the constructor, not an instantiated object
var loadPage = function(url, view) {
$.mobile.loadPage(url, true).done(function (absUrul, options, page) {
var v,
pageId = page.attr('id');
v = new view({
el: page
});
...
}
}
You now call loadPage() like so:
loadPage('login.html', LoginView);
This gives Backbone.View the $el which to delegate your events.
I'm working on a single page application using jquery mobile along with knockout.js binding on MVC4 platform..
This is my button on the main div page:
<div data-role="page" id="pageMain">
<div data-role="content">
View Invoices
</div>
</div>
This is my target div page:
<div data-role="page" id="pageExisting">
<div data-role="header">
<h1>Existing Invoices's</h1>
<a data-rel="back" data-role="button">Back</a>
</div>
<div class="choice_list" data-role="content" data-bind="visible: Headers().length > 0">
<ul id="headersList" data-role="listview" data-bind="foreach: Headers" data-filter-placeholder="Search Invoice" data-filter-theme="a" data-inset="true"
data-filter="true" data-theme="b">
<li>
<a href="#" data-inline="true">
<h2>Invoice No.: <span data-bind="text: inv_no"></span></h2>
<p>Amount.: <span data-bind="text: inv_amt"></span></p>
</a>
</ul>
</div>
</div>
Here is the script section:
var HeaderViewModel = function () {
//Make the self as 'this' reference
var self = this;
self.Headers = ko.observableArray([]);
function GetHeaders() {
//Ajax Call Get All Employee Records
// self.GetHeaders = function () {
$.ajax({
type: "GET",
url: "/api/InvAPI",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (data) {
self.Headers(data);
},
complete: function () {
$.mobile.changePage("#pageExisting");
},
error: function (error) {
alert(error.status + "<--and--> " + error.statusText);
}
});
}
self.GetHeader = function () {
GetHeaders();
}
};
$(document).ready(function () {
ko.applyBindings(new HeaderViewModel());
}
When I hit the button "view Invoices" I get a typical Jquery mobile formated listview
but the problem is when I hit the back button and navigate again to the "PageExisiting" div Page I get the list data with no styling..
When viewing page source in both cases, I noticed that on second navigation;
Li tags have not attributes.
I have tried some solutions like: listview refresh, page destroy, page create prior to the line:
$.mobile.changePage("#pageExisting");
with no luck.
I'm stuck here guys and I will appreciate your proposed solutions
Thanks
Refresh list view before showing the page.
$('#pageID').on('pagebeforeshow', function() {
$('[data-role=listview]').listview('refresh');
});
I've been trying to use backbonejs and jqm together.
I can render the main page alright. The page has a list that the user can tap on. The item selected should show a detail page with info on the list item selected. The detail page is a backbone view with a template that's rendered in the item's view object.
The detail's view .render() produces the html ok and I set the html of the div tag of the main page to the rendered item's detail markup. It looks like this:
podClicked: function (event) {
console.log("PodListItemView: got click from:" + event.target.innerHTML + " id:" + (this.model.get("id") ? this.model.get("id") : "no id assigned") + "\n\t CID:" + this.model.cid);
var detailView = new PodDetailView({ model: this.model });
detailView.render();
},
The detail view's render looks like this:
render: function () {
this.$el.html(this.template({ podId: this.model.get("podId"), isAbout_Name: this.model.get("isAbout_Name"), happenedOn: this.model.get("happenedOn") }));
var appPageHtml = $(app.el).html($(this.el));
$.mobile.changePage(""); // <-- vague stab in the dark to try to get JQM to do something. I've also tried $.mobile.changePage(appPageHtml).
console.log("PodDetailView: render");
return this;
}
I can see that the detail's view has been rendered on the page by checking Chrome's dev tools html editor but it's not displaying on the page. All I see is a blank page.
I've tried $.mobile.changePage() but, without an URL it throws an error.
How do I get JQM to apply it's class tags to the rendered html?
the HTML and templates look like this:
<!-- Main Page -->
<div id="lessa-app" class="meditator-image" data-role="page"></div>
<!-- The rest are templates processed through underscore -->
<script id="app-main-template" type="text/template">
<div data-role="header">
<h1>#ViewBag.Title</h1>
</div>
<!-- /header -->
<div id="main-content" data-role="content">
<div id="pod-list" data-theme="a">
<ul data-role="listview" >
</ul>
</div>
</div>
<div id="main-footer" data-role='footer'>
<div id="newPod" class="ez-icon-plus"></div>
</div>
</script>
<script id="poditem-template" type="text/template">
<span class="pod-listitem"><%= isAbout_Name %></span> <span class='pod-listitem ui-li-aside'><%= happenedOn %></span> <span class='pod-listitem ui-li-count'>5</span>
</script>
<script id="page-pod-detail-template" type="text/template">
<div data-role="header">
<h1>Pod Details</h1>
</div>
<div data-role="content">
<div id='podDetailForm'>
<fieldset data-role="fieldcontain">
<legend>PodDto</legend>
<label for="happenedOn">This was on:</label>
<input type="date" name="name" id="happenedOn" value="<%= happenedOn %>" />
</fieldset>
</div>
<button id="backToList" data-inline="false">Back to list</button>
</div>
<div data-role='footer'></div>
</script>
Thanks in advance for any advice... is this even doable?
I've finally found a way to do this. My original code has several impediments to the success of this process.
The first thing to do is to intercept jquerymobile's (v.1.2.0) changePage event like this:
(I've adapted the outline from jqm's docs and left in the helpful comments: see http://jquerymobile.com/demos/1.2.0/docs/pages/page-dynamic.html
)
$(document).bind("pagebeforechange", function (e, data) {
// We only want to handle changePage() calls where the caller is
// asking us to load a page by URL.
if (typeof data.toPage === "string") {
// We are being asked to load a page by URL, but we only
// want to handle URLs that request the data for a specific
// category.
var u = $.mobile.path.parseUrl(data.toPage),
re = /^#/;
// don't intercept urls to the main page allow them to be managed by JQM
if (u.hash != "#lessa-app" && u.hash.search(re) !== -1) {
// We're being asked to display the items for a specific category.
// Call our internal method that builds the content for the category
// on the fly based on our in-memory category data structure.
showItemDetail(u, data.options); // <--- handle backbone view.render calls in this function
// Make sure to tell changePage() we've handled this call so it doesn't
// have to do anything.
e.preventDefault();
}
}
});
The changePage() call is made in the item's list backbone view events declaration which passes to the podClicked method as follows:
var PodListItemView = Backbone.View.extend({
tagName: 'li', // name of (orphan) root tag in this.el
attributes: { 'class': 'pod-listitem' },
// Caches the templates for the view
listTemplate: _.template($('#poditem-template').html()),
events: {
"click .pod-listitem": "podClicked"
},
initialize: function () {
this.model.bind('change', this.render, this);
this.model.bind('destroy', this.remove, this);
},
render: function () {
this.$el.html(this.listTemplate({ podId: this.model.get("podId"), isAbout_Name: this.model.get("isAbout_Name"), happenedOn: this.model.get("happenedOn") }));
return this;
},
podClicked: function (event) {
$.mobile.changePage("#pod-detail-page?CID='" + this.model.cid + "'");
},
clear: function () {
this.model.clear();
}
});
In the 'showItemDetail' function the query portion of the url is parsed for the CID of the item's backbone model. Again I've adapted the code provided in the jquerymobile.com's link shown above.
Qestion: I have still figuring out whether it's better to have the code in showItemDetail() be inside the view's render() method. Having a defined function seems to detract from backbone's architecture model. On the other hand, having the render() function know about calling JQM changePage seems to violate the principle of 'separation of concerns'. Can anyone provide some insight and guidance?
// the passed url looks like #pod-detail-page?CID='c2'
function showItemDetail(urlObj, options) {
// Get the object that represents the item selected from the url
var pageSelector = urlObj.hash.replace(/\?.*$/, "");
var podCid = urlObj.hash.replace(/^.*\?CID=/, "").replace(/'/g, "");
var $page = $(pageSelector),
// Get the header for the page.
$header = $page.children(":jqmData(role=header)"),
// Get the content area element for the page.
$content = $page.children(":jqmData(role=content)");
// The markup we are going to inject into the content area of the page.
// retrieve the selected pod from the podList by Cid
var selectedPod = podList.getByCid(podCid);
// Find the h1 element in our header and inject the name of the item into it
var headerText = selectedPod.get("isAbout_Name");
$header.html("h1").html(headerText);
// Inject the item info into the content element
var view = new PodDetailView({ model: selectedPod });
var viewElHtml = view.render().$el.html();
$content.html(viewElHtml);
$page.page();
// Enhance the listview we just injected.
var fieldContain = $content.find(":jqmData(role=listview)");
fieldContain.listview();
// We don't want the data-url of the page we just modified
// to be the url that shows up in the browser's location field,
// so set the dataUrl option to the URL for the category
// we just loaded.
options.dataUrl = urlObj.href;
// Now call changePage() and tell it to switch to
// the page we just modified.
$.mobile.changePage($page, options);
}
So the above provides the event plumbing.
The other problem I had was that the page was not set up correctly. It's better to put the page framework in the main html and not put it in an underscore template to be rendered at a later time. I presume that avoids issues where the html is not present when jqm takes over.
<!-- Main Page -->
<div id="lessa-app" data-role="page">
<div data-role="header">
<h1></h1>
</div>
<!-- /header -->
<div id="main-content" data-role="content">
<div id="pod-list" data-theme="a">
<ul data-role="listview">
</ul>
</div>
</div>
<div id="main-footer" data-role='footer'>
<div id="main-newPod" class="ez-icon-plus"></div>
</div>
</div>
<!-- detail page -->
<div id="pod-detail-page" data-role="page">
<div data-role="header">
<h1></h1>
</div>
<div id="detail-content" data-role="content">
<div id="pod-detail" data-theme="a">
</div>
</div>
<div id="detail-footer" data-role='footer'>
back
</div>
</div>
Hi i'm developping a simple listView that lists the column "firstname" of my table : i want to get the selected value (name) , i found this link but it shows he how to get the index and not the value of the selected item http://jsfiddle.net/w2JZU/
here's my code :
HTML:
<div id="popup-bg">
</div>
<div id="popup-box">
<div data-role="page" id="home">
<div data-role="header">
<h1>Players</h1>
</div>
<div data-role="content">
<ul data-role="listview" id="artiste" >
</ul>
</div>
</div>
</div>
</section>
js :
function successCB()
{
db.transaction(queryDB, errorCB);
}
function queryDB(tx)
{
tx.executeSql('SELECT * FROM Players ', [], querySuccess, errorCB);
}
function querySuccess(tx, results)
{
var len = results.rows.length;
var dataset= results.rows;
$("#artiste").empty();
for (var i = 0; i < len; i++)
{ item = dataset.item(i);
$("#artiste").append( "<li data-theme='c'><a href='game.html'>
<img src='images/avatar.jpg'><h3>"+item['firstName']+"</h3></a></li>" );
}
There isn't a "value" for an item in a list using the li tag. However, you can get the text of what's in that list element using the jQuery .text() method. I've modified the jsfiddle you referenced to do exactly that: http://jsfiddle.net/Cht2e/
You might want to consider adding another attribute to the li tag, such as data-name (you can make up the attribute) and then you can get that via the jQuery .attr() method. For example, you might change you append code to do:
$("#artiste").append( "<li data-theme='c' data-name='"+item['firstName']+"'><a href='game.html'>
<img src='images/avatar.jpg'><h3>"+item['firstName']+"</h3></a></li>" );
And then attach your click handler like this:
$('#artiste').children('li').on('click', function () {
alert('Selected Name=' + $(this).attr('data-name'));
});
I don't think this is necessarily the best structure or approach to take, but it will accomplish what you're asking.