Sorting an array and saving to Mongo doesn't seem to trigger reactivity on my page - jquery-ui

I have a template that I am loading from a route like so:
this.route('formEdit', {
path: '/admin/form/:_id',
data: function() { return Forms.findOne({_id: this.params._id}); },
onBeforeAction: function() { AccountUtils.authenticationRequired(this, ['ADMIN']); }
});
In which I have a template defined like:
<template name="formEdit">
<div id="formContainer">
...
{{#each header_fields}}
<div class="sortable">
{{> headerFieldViewRow }}
</div>
{{/each}}
</div>
</template>
And:
<template name="headerFieldViewRow">
{{#with header_field}}
...
{{/with}}
</template>
I then make the container around all the header fields sortable using jQuery UI Sortable:
Template.formEdit.rendered = function() {
$('.sortable').sortable({
axis: "y",
stop: function(event, ui) {
var form = Blaze.getData($('#formContainer')[0]);
var newFormHeaders = [];
$('#headerFieldsTable div.headerField').each(function(idx, headerFieldDiv) {
var header = Blaze.getData(headerFieldDiv);
header.sequence = idx;
Meteor.call('saveHeaderField', header);
newFormHeaders.push({header_field_id: header._id});
});
form.header_fields = newFormHeaders;
Meteor.call('saveForm', form);
}
});
}
Basically, when sorting stops, loop through all the headers, getting the data for each and updating the sequence number, then re-build the array in Forms and save them back. In the server code I have printouts for the two save calls, and the do properly print out the correct order of both the headers and the form.
The problem I am running into is that, after sorting, the visual display of the form and it's headers "snaps" back to the pre-sorted state, even though the data in the DB is correct. If I simply reload the form, either by hitting enter in the Address bar or by simply re-loading it from the menu, everything is displayed correctly. It's as if the reactive piece isn't working.
I have noted that I am getting an error when I update the client code in my server log that reads:
=> Client modified -- refreshing
I20141010-18:25:47.017(-4)? Failed to receive keepalive! Exiting.
=> Exited with code: 1
I don't think this is related as I was getting that error prior to adding this sorting code.
Update: Adding code for saveForm and saveHeader
saveForm:
// Saves the Form to the DB
saveForm: function(params) {
// Structure the data to send
var formEntry = {
title: params.title,
client_id: params.client_id,
header_fields: params.header_fields,
form_fields: params.form_fields,
created_by: Meteor.userId(),
created_on: new Date()
};
if (params._id == null) {
console.log("Saving new Form entry: %j", formEntry);
formEntry._id = Forms.insert(formEntry);
} else {
formEntry._id = params._id;
console.log("Updating Form entry: %j", formEntry);
Forms.update({_id: formEntry._id}, formEntry);
}
return formEntry;
}
saveHeader:
// Saves the HeaderField to the DB
saveHeaderField: function(params) {
// Structure the data to send
var headerFieldEntry = {
label: params.label,
field_type: params.field_type,
field_options: params.field_options,
form_id: params.form_id,
required: params.required,
allows_pictures: params.allows_pictures,
record_geo: params.record_geo
};
if (params._id == null) {
console.log("Saving new HeaderField entry: %j", headerFieldEntry);
headerFieldEntry._id = HeaderFields.insert(headerFieldEntry);
} else {
headerFieldEntry._id = params._id;
console.log("Updating HeaderField entry: %j", headerFieldEntry);
HeaderFields.update({_id: headerFieldEntry._id}, headerFieldEntry);
}
return headerFieldEntry;
}

I think the issue here is that Meteor.call will run on the server - you either need to use a callback or invalidate your template, if you want to return a value Meteor.call. From the docs:
On the client, if you do not pass a callback and you are not inside a stub, call will return undefined, and you will have no way to get the return value of the method. That is because the client doesn't have fibers, so there is not actually any way it can block on the remote execution of a method.
There is more info in this answer and this answer and the Meteor.call docs.
Hope that helps!

Related

Retrieving input form data Trello power-up

I have created a custom power up that requires settings. I use the show-settings capability for this.
The settings form is shown successfully:
But I don't know how to retrieve data from that form. Getting and setting is for saving en retrieving data, as I understand, but not for getting http params.
I've tried to get parameters like:
function get(parameterName) {
var result = null,
tmp = [];
location.search
.substr(1)
.split("&")
.forEach(function(item) {
tmp = item.split("=");
if (tmp[0] === parameterName) result = decodeURIComponent(tmp[1]);
});
return result;
}
Then calling get('webhookUrl') in different places;
outside of window.TrelloPowerUp.initialize({});
inside 'show-settings': function(t, options) {
inside callback in t.popup(..)
Anything I log there, it doesn't seem to work.
app.js:
...
'show-settings': function(t, options) {
// when a user clicks the gear icon by your Power-Up in the Power-Ups menu
// what should Trello show. We highly recommend the popup in this case as
// it is the least disruptive, and fits in well with the rest of Trello's UX
return t.popup({
title: 'Custom Fields Settings',
url: 'settings.html?v=' + timeStamp,
height: 184, // we can always resize later
callback: function(t) {
console.log(t);
}
});
},
In the settings.html
<body>
<form id="settings">
<input name="webhookUrl" placeholder="https://app.buddy.works/user-name/projects-name/pipelines/pipeline .."></input>
<button type="submit" class="mod-primary">Save</button>
</form>
</body>
Btw, i find it hard to debug things in the browser, in Chrome Developers Tools I check Javascript Context, every time I scroll to the right iframe, it is tedious.

Select2 AJAX doesn't update when changed programatically

I have a Select2 that fetches its data remotely, but I would also like to set its value programatically. When trying to change it programatically, it updates the value of the select, and Select2 notices the change, but it doesn't update its label.
https://jsfiddle.net/Glutnix/ut6xLnuq/
$('#set-email-manually').click(function(e) {
e.preventDefault();
// THIS DOESN'T WORK PROPERLY!?
$('#user-email-address') // Select2 select box
.empty()
.append('<option selected value="test#test.com">test#test.com</option>');
$('#user-email-address').trigger('change');
});
I've tried a lot of different things, but I can't get it going. I suspect it might be a bug, so have filed an issue on the project.
reading the docs I think maybe you are setting the options in the wrong way, you may use
data: {}
instead of
data, {}
and set the options included inside {} separated by "," like this:
{
option1: value1,
option2: value2
}
so I have changed this part of your code:
$('#user-email-address').select2('data', {
id: 'test#test.com',
label: 'test#test.com'
});
to:
$('#user-email-address').select2({'data': {
id: 'test#test.com',
label: 'test#test.com'
}
});
and the label is updating now.
updated fiddle
hope it helps.
Edit:
I correct myself, it seems like you can pass the data the way you were doing data,{}
the problem is with the data template..
reading the docs again it seems that the data template should be {id, text} while your ajax result is {id, email}, the set manual section does not work since it tries to return the email from an object of {id, text} with no email. so you either need to change your format selection function to return the text as well instead of email only or remap the ajax result.
I prefer remapping the ajax results and go the standard way since this will make your placeholder work as well which is not working at the moment because the placeholder template is {id,text} also it seems.
so I have changed this part of your code:
processResults: function(data, params) {
var payload = {
results: $.map(data, function(item) {
return { id: item.email, text: item.email };
})
};
return payload;
}
and removed these since they are not needed anymore:
templateResult: function(result) {
return result.email;
},
templateSelection: function(selection) {
return selection.email;
}
updated fiddle: updated fiddle
For me, without AJAX worked like this:
var select = $('.user-email-address');
var option = $('<option></option>').
attr('selected', true).
text(event.target.value).
val(event.target.id);
/* insert the option (which is already 'selected'!) into the select */
option.appendTo(select);
/* Let select2 do whatever it likes with this */
select.trigger('change');
Kevin-Brown on GitHub replied and said:
The issue is that your templating methods are not falling back to text if email is not specified. The data objects being passed in should have the text of the <option> tag in the text property.
It turns out the result parameter to these two methods have more data in them than just the AJAX response!
templateResult: function(result) {
console.log('templateResult', result);
return result.email || result.text;
},
templateSelection: function(selection) {
console.log('templateSelection', selection);
return selection.email || selection.id;
},
Here's the fully functional updated fiddle.

backbone.js load text file into a collection

This is first time I am using a framework for development and stuck with the very first step.
I am converting a Flex application to Javascript application and using backbone as framework.
I have to load a text file which is in name value format.
<!doctype html>
<html>
<head>
<script src='http://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js'></script>
<script src='http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js'></script>
<script src='http://cdnjs.cloudflare.com/ajax/libs/backbone.js/0.9.10/backbone-min.js'></script>
<script>
var ResourceBundleCollection = Backbone.Collection.extend({
url:'ResourceBundle.txt',
});
var resourceBundleCollection = new ResourceBundleCollection();
resourceBundleCollection.fetch();
</script>
</head>
<body>
</body>
</html>
The ResourceBundle.txt includes the content in following format
location_icon=../../abc/test.png
right_nav_arrow_image=assets/images/arrow.png
right_nav_arrow_image_visible=true
It is throwing following error
not well-formed
I could load the text file easily using JQuery and parse it
$.ajax({
type : "GET",
url : "ResourceBundle.txt",
datatype : "script",
success : resourceXMLLoaded
});
and parse it using the following code
var lines = txt.split("\n");
for(var i=0;i<lines.length;i++) {
if(lines[i].length > 5) {
var _arr = lines[i].split("=");
resourceBundleObj[$.trim(_arr[0])] = $.trim(_arr[1]);
}
}
Please advice how to achieve the same results in backbone.js
If you MUST use plain text to support this, you can override Backbone.Collection.parse to achieve what you need.
In addition to that, you may also want to create a ResourceBundleModel to host each item in the ResourceBundleCollection.
You can see a demo here: http://jsfiddle.net/dashk/66nkF/
Code for Model & Collection is here:
// Define a Backbone.Model that host each ResourceBundle
var ResourceBundleModel = Backbone.Model.extend({
defaults: function() {
return {
name: null,
value: null
};
}
});
// Define a collection of ResourceBundleModels.
var ResourceBundleCollection = Backbone.Collection.extend({
// Each collection should know what Model it works with, though
// not mandated, I guess this is best practice.
model: ResourceBundleModel,
// Replace this with your URL - This is just so we can demo
// this in JSFiddle.
url: '/echo/html/',
parse: function(resp) {
// Once AJAX is completed, Backbone will call this function
// as a part of 'reset' to get a list of models based on
// XHR response.
var data = [];
var lines = resp.split("\n");
// I am just reusing your parsing logic here. :)
for (var i=0; i<lines.length; i++) {
if (lines[i].length > 5) {
var _arr = lines[i].split("=");
// Instead of putting this into collection directly,
// we will create new ResourceBundleModel to contain
// the data.
data.push(new ResourceBundleModel({
name: $.trim(_arr[0]),
value: $.trim(_arr[1])
}));
}
}
// Now, you've an array of ResourceBundleModel. This set of
// data will be used to construct ResourceBundleCollection.
return data;
},
// Override .sync so we can demo the feature on JSFiddle
sync: function(method, model, options) {
// When you do a .fetch, method is 'read'
if (method === 'read') {
var me = this;
// Make an XHR request to get data
// Replace this code with your own code
Backbone.ajax({
url: this.url,
method: 'POST',
data: {
// Feed mock data into JSFiddle's mock XHR response
html: $('#mockData').text()
},
success: function(resp) {
options.success(me, resp, options);
},
error: function() {
if (options.error) {
options.error();
}
}
});
}
else {
// Call the default sync method for other sync method
Backbone.Collection.prototype.sync.apply(this, arguments);
}
}
});
Backbone is designed to work with a RESTful API through JSON natively. It however is a library that is flexible enough to fit your need, given enough customization.
By default a collection in Backbone will only accept a JSON formatted collection.
So you need to convert your input to JSON format:
[{"name": "name", "value": "value},
{"name": "name", "value": "value}, ...
]
Of course you can override the default behaviour:
Overriding backbone's parse function

Lift - Autocomplete with Ajax Submission

I would like to use an autocomplete with ajax. So my goal is to have:
When the user types something in the text field, some suggestions provided by the server appear (I have to find suggestions in a database)
When the user presses "enter", clicks somewhere else than in the autocomplete box, or when he/she selects a suggestion, the string in the textfield is sent to the server.
I first tried to use the autocomplete widget provided by lift but I faced three problems:
it is meant to be an extended select, that is to say you can originally only submit suggested values.
it is not meant to be used with ajax.
it gets buggy when combined with WiringUI.
So, my question is: How can I combine jquery autocomplete and interact with the server in lift. I think I should use some callbacks but I don't master them.
Thanks in advance.
UPDATE Here is a first implementation I tried but the callback doesn't work:
private def update_source(current: String, limit: Int) = {
val results = if (current.length == 0) Nil else /* generate list of results */
new JsCmd{def toJsCmd = if(results.nonEmpty) results.mkString("[\"", "\", \"", "\"]") else "[]" }
}
def render = {
val id = "my-autocomplete"
val cb = SHtml.ajaxCall(JsRaw("request"), update_source(_, 4))
val script = Script(new JsCmd{
def toJsCmd = "$(function() {"+
"$(\"#"+id+"\").autocomplete({ "+
"autocomplete: on, "+
"source: function(request, response) {"+
"response("+cb._2.toJsCmd + ");" +
"}"+
"})});"
})
<head><script charset="utf-8"> {script} </script></head> ++
<span id={id}> {SHtml.ajaxText(init, s=>{ /*set cell to value s*/; Noop}) } </span>
}
So my idea was:
to get the selected result via an SHtml.ajaxText field which would be wraped into an autocomplete field
to update the autocomplete suggestions using a javascript function
Here's what you need to do.
1) Make sure you are using Lift 2.5-SNAPSHOT (this is doable in earlier versions, but it's more difficult)
2) In the snippet you use to render the page, use SHtml.ajaxCall (in particular, you probably want this version: https://github.com/lift/framework/blob/master/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala#L170) which will allow you to register a server side function that accepts your search term and return a JSON response containing the completions. You will also register some action to occur on the JSON response with the JsContext.
3) The ajaxCall above will return a JsExp object which will result in the ajax request when it's invoked. Embed it within a javascript function on the page using your snippet.
4) Wire them up with some client side JS.
Update - Some code to help you out. It can definitely be done more succinctly with Lift 2.5, but due to some inconsistencies in 2.4 I ended up rolling my own ajaxCall like function. S.fmapFunc registers the function on the server side and the function body makes a Lift ajax call from the client, then invokes the res function (which comes from jQuery autocomplete) on the JSON response.
My jQuery plugin to "activate" the text input
(function($) {
$.fn.initAssignment = function() {
return this.autocomplete({
autoFocus: true,
source: function(req, res) {
search(req.term, res);
},
select: function(event, ui) {
assign(ui.item.value, function(data){
eval(data);
});
event.preventDefault();
$(this).val("");
},
focus: function(event, ui) {
event.preventDefault();
}
});
}
})(jQuery);
My Scala code that results in the javascript search function:
def autoCompleteJs = JsRaw("""
function search(term, res) {
""" +
(S.fmapFunc(S.contextFuncBuilder(SFuncHolder({ terms: String =>
val _candidates =
if(terms != null && terms.trim() != "")
assigneeCandidates(terms)
else
Nil
JsonResponse(JArray(_candidates map { c => c.toJson }))
})))
({ name =>
"liftAjax.lift_ajaxHandler('" + name
})) +
"=' + encodeURIComponent(term), " +
"function(data){ res(data); }" +
", null, 'json');" +
"""
}
""")
Update 2 - To add the function above to your page, use a CssSelector transform similar to the one below. The >* means append to anything that already exists within the matched script element. I've got other functions I've defined on that page, and this adds the search function to them.
"script >*" #> autoCompleteJs
You can view source to verify that it exists on the page and can be called just like any other JS function.
With the help of Dave Whittaker, here is the solution I came with.
I had to change some behaviors to get:
the desired text (from autocomplete or not) in an ajaxText element
the possibility to have multiple autocomplete forms on same page
submit answer on ajaxText before blurring when something is selected in autocomplete suggestions.
Scala part
private def getSugggestions(current: String, limit: Int):List[String] = {
/* returns list of suggestions */
}
private def autoCompleteJs = AnonFunc("term, res",JsRaw(
(S.fmapFunc(S.contextFuncBuilder(SFuncHolder({ terms: String =>
val _candidates =
if(terms != null && terms.trim() != "")
getSugggestions(terms, 5)
else
Nil
JsonResponse(JArray(_candidates map { c => JString(c)/*.toJson*/ }))
})))
({ name =>
"liftAjax.lift_ajaxHandler('" + name
})) +
"=' + encodeURIComponent(term), " +
"function(data){ res(data); }" +
", null, 'json');"))
def xml = {
val id = "myId" //possibility to have multiple autocomplete fields on same page
Script(OnLoad(JsRaw("jQuery('#"+id+"').createAutocompleteField("+autoCompleteJs.toJsCmd+")"))) ++
SHtml.ajaxText(cell.get, s=>{ cell.set(s); SearchMenu.recomputeResults; Noop}, "id" -> id)
}
Script to insert into page header:
(function($) {
$.fn.createAutocompleteField = function(search) {
return this.autocomplete({
autoFocus: true,
source: function(req, res) {
search(req.term, res);
},
select: function(event, ui) {
$(this).val(ui.item.value);
$(this).blur();
},
focus: function(event, ui) {
event.preventDefault();
}
});
}
})(jQuery);
Note: I accepted Dave's answer, mine is just to provide a complete answer for my purpose

jquerymobile and AJAX

seems to me that I didn't fully understand the concept behind jquerymobile, because I have no idea how to solve this issue.
What I want to do is load some HTML Content via AJAX, according to location.hash, put it into a new page and load this page.
But if I create a page myself by using the pagebeforechange event, jquerymobile just ignores it, creates its own div and my content won't be displayed.
How do I have to do it?
Edit:
This is how I am currently doing it, but it wont't work.
$(function() {
getPageContent(top.location.href, false);
$(document).bind( "pagebeforechange", function( e, data ) {
getPageContent(data.toPage, true);
});
});
function getPageContent(pageUrl, changedPage) {
var re = /.*\/#(.*)/;
var result;
result = re.exec(pageUrl);
window.page = result[1].substr(0,3);
window.id = result[1].substr(3);
window.ajaxUrl = "request.php?page="+window.page+"&id="+window.id;
$.ajax({
url: window.ajaxUrl,
success: function(data) {
if(data.error) {
alert(data.error);
}
else if(data.data) {
if(changedPage) {
changePage(data.data));
}
else {
$('#content[role="main"]').html(atob(data.data));
setupPage();
}
}
else {
alert("UNKNOWN ERROR: "+data);
}
}
});
}
function changePage(html) {
var div = "<div></div>";
var newPage = $(div).attr("data-role", "page").attr("data-url", window.page+window.id);
var header = $(div).attr("data-role", "header");
var content = $(div).attr("data-role", "content");
var footer = $(div).attr("data-role", "footer");
$("body").append(newPage);
newPage.append(header, content, footer);
content.html(html);
newPage.page();
}
Complete edit of the whole answer:
First. Set your body id to id=body. Then when you want to load the new page and change to it, use an ajax call like this:
$.get(window.ajaxUrl, function(data){
$('#body').append("<div id='newPage' data-role='page'></div>"); //Creates a new page.
$('#newPage').html(data); //Loads the html content into the new page.
$.mobile.changePage('#newPage'); //Navigates to the new page.
}
This sends an ajax call with the method GET to the url found in your window.ajaxUrl. If the call is successful, it creates a new page named "newPage", and fills it with the data received from the ajax call. Then redirects to the newly created page.
This jsFiddle shows the basics of how it works. However, it doesn't use any ajax call.
You have to refresh the page with jQueryMobile :
$("#your-page").trigger("create");
--Edit
<script>
$("#thepage").live("pageshow", function(){
$("#thepage).trigger("create");
});
</script>
Change the content of #thepage before 'pageshow' event
It does this for you automatically - just make a regular link to the page and jquery mobile will shwo the loading spinner, load it in the background via ajax, then transition to the new page.
Make sure all your pages are decide with unique IDs and data-role='page'. Check out the start guide here:
http://jquerymobile.com/demos/1.1.0/docs/about/getting-started.html

Resources