I'm starting to develop a mobile app with jQuery Mobile. The idea is to build HTML static pages, and before showing them, call to the server to obtain the i18n text for the input labels and buttons. I mark the HTML elements that are susceptible to change the inner text with an special attribute: "data-i18n":
For a label:
<label data-i18n="login.username" for="loginPaciente.username">login.username</label>
For a button:
<button data-i18n="login.submit" type="submit" data-theme="a">login.submit</button>
I call to the server using JSON:
$('#pageLogin').live('pagebeforeshow',function(event, ui){
var action = "/MyServerApp/namespace1/mobile_Action_Login_configPage.action";
$.getJSON(action, function(data) {
var resources = data.i18n_resources;
var id, text;
var $scope = $('#pageLogin');
for (i=0; i<resources.length; i++){
id = resources[i].id;
text = resources[i].text;
$scope.find('[data-i18n="' + id + '"]').html(text);
}
});
});
This works perfectly with the labels, because JQM doesn't modify these HTML elements. The problem comes with the button, because JQM hides the button I've defined, and creates a new span to render the button. When I read the JSON result, I can find and change the button I've defined, but not the new span that JQM has created, so the text that appears on screen is the old one: "login.submit".
Is there any way to execute the JSON call before JQM changes the HTML code?
P.D.: The reason for not building the whole HTML page dynamically (including i18n texts) is that in the future, I want to encapsulate the web app with PhoneGap or a similar shell, and I want to distribute the HTML pages, CSS and scripts inside the application, and minimize the data traffic with the server.
Thanks in advance:
Carlos.
EDIT: invoking $scope.trigger('create') after changing the text doesn't solve the problem.
Finally I've found the solution to my problems by myself, catching the event "pagebeforecreate".
I invoke this function on each page I need to internationalize, passing the server action I need to call and the page id:
function utils_loadConfigPage(action, pageid){
$(document).bind("pagebeforecreate", function(){
var $page = $('#' + pageid);
var _action = action;
var paramCallback = "jsoncallback=?";
var concat = "?";
if (_action.indexOf("?")!=-1){
concat = "&";
}
_action += concat + paramCallback;
$.ajaxSetup({"async": false});
$.getJSON(_action, function(data){
utilis_doConfigPage(data, $page);
});
$.ajaxSetup({"async": true});
});
}
Note that I force to use synchronous calls to the server, in order to avoid the page mobile enhancing before the i18n texts were ready.
This is the function that is invoked in the json callback:
function utils_doConfigPage(data, $scope){
utils_seti18nTexts(data, $scope);
utils_setPlaceholders($scope);
}
This function finds all i18n elements and override their inner html with the translated texts:
function utils_seti18nTexts(data, $scope){
var resources = data.i18n_resources;
var id, text;
for (i=0; i<resources.length; i++){
id = resources[i].id;
text = resources[i].text;
$scope.find('[data-i18n="' + id + '"]').html(text);
}
}
This function overrides the placeholder texts for the inputs:
function utils_setPlaceholders($scope){
$scope.find('div[data-role="fieldcontain"].ui-hide-label').each(function(){
var textLabel = $(this).find('label').html();
$(this).find('.placeholder').attr('placeholder', textLabel);
});
}
And finally, this is the jsp that produces the i18n resources. I use Struts2, so the jsp is not invoked directly. I invoke an action and the jsp is only the view. The i18n resources are obtained using Struts2 capabilities:
<%# page contentType="application/json; charset=UTF-8" pageEncoding="UTF-8" %>
<%# taglib prefix="s" uri="/struts-tags"%>
<s:property value="jsoncallback" />({
"i18n_resources":
[
{
"id" : "MOBILE_APP_NAME",
"text" : "<s:text name="APP_NAME" />"
}
,{
"id" : "TITLE_LOGIN",
"text" : "<s:text name="TITLE_LOGIN" />"
}
,{
"id" : "LOGIN_USERNAME",
"text" : "<s:text name="LOGIN_USERNAME" />"
}
,{
"id" : "LOGIN_PASSWORD",
"text" : "<s:text name="LOGIN_PASSWORD" />"
}
,{
"id" : "BUTTON_OK",
"text" : "<s:text name="BUTTON_OK" />"
}
,{
"id" : "MOBILE_APP_FOOTER",
"text" : "<s:text name="MOBILE_APP_FOOTER" />"
}
]
})
I don't know if this is the best way to internationalize a JQM application. Any suggestion will be apreciated.
You can just change the text of the <span> element with the ui-btn-text class:
$scope.find('[data-i18n="' + id + '"]').find('.ui-btn-text').html(text);
Or if you aren't sure if the element will have been initialized by jQuery Mobile you can check for the existence of the ui-btn-class first:
var $btn_text = $scope.find('[data-i18n="' + id + '"]').find('.ui-btn-text');
if ($btn_text.length > 0) {
$btn_text.html(text);
} else {
$scope.find('[data-i18n="' + id + '"]').html(text);
}
Related
I want to do autocomplete for textarea using entered values from browser. It is working for Textbox but not working Text area.
Normal textbox indeed get autocomplete behaviour for free.
As far as i know, you can get similar behaviour for textarea (even better, with all history) with installing lazarus plugin in your web browser.
Once installed, you will get a small cross icon on the top right corner. Clicking it will popup previous entries.
I usually don't like to install third party plugin in my web browser but this can save a lot of time and frustration when accidentally loosing all the text we already type.
First you need to include jquery UI then use the example code
HTML
<div class="ui-widget">
<label for="tags">Tags:</label>
<textarea id="tags" size="30"></textarea>
</div>
JS
$(function () {
$("document").ready(function () {
var availableTags = [
"ActionScript",
"AppleScript",
"Asp",
"BASIC",
"C",
"C++",
"Clojure",
"COBOL",
"ColdFusion",
"Erlang",
"Fortran",
"Groovy",
"Haskell",
"Java",
"JavaScript",
"Lisp",
"Perl",
"PHP",
"Python",
"Ruby",
"Scala",
"Scheme"];
$("#tags").on("keydown", function () {
var newY = $(this).textareaHelper('caretPos').top + (parseInt($(this).css('font-size'), 10) * 1.5);
var newX = $(this).textareaHelper('caretPos').left;
var posString = "left+" + newX + "px top+" + newY + "px";
$(this).autocomplete("option", "position", {
my: "left top",
at: posString
});
});
$("#tags ").autocomplete({
source: availableTags
});
});
});
You Need to use external plugin
Scripts and CSS
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<link rel="stylesheet" type="text/css" href="http://code.jquery.com/ui/1.10.4/themes/ui-lightness/jquery-ui.css"/>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/jquery-ui.min.js"></script>
HTML
<textarea id="demo"></textarea>
Script
<script>
$(function() {
//Get the Data from a JSON or Hidden Feild
var availableTags = ["jQuery.com", "jQueryUI.com", "jQueryMobile.com", "jQueryScript.net", "jQuery", "Free jQuery Plugins"]; // array of autocomplete words
var minWordLength = 2;
function split(val) {
return val.split(' ');
}
function extractLast(term) {
return split(term).pop();
}
$("#demo") // jQuery Selector
// don't navigate away from the field on tab when selecting an item
.bind("keydown", function(event) {
if (event.keyCode === $.ui.keyCode.TAB && $(this).data("ui-autocomplete").menu.active) {
event.preventDefault();
}
}).autocomplete({
minLength: minWordLength,
source: function(request, response) {
// delegate back to autocomplete, but extract the last term
var term = extractLast(request.term);
if(term.length >= minWordLength){
response($.ui.autocomplete.filter( availableTags, term ));
}
},
focus: function() {
// prevent value inserted on focus
return false;
},
select: function(event, ui) {
var terms = split(this.value);
// remove the current input
terms.pop();
// add the selected item
terms.push(ui.item.value);
// add placeholder to get the comma-and-space at the end
terms.push("");
this.value = terms.join(" ");
return false;
}
});
});
</script>
DEMO LINK
ANOTHER PLUGIN TEXTEXTJS
Browsers do not currently support autocompletion for a textarea. The autocomplete attribute is formally allowed for textarea in HTML5 and it has the default value of on, but this value just means that browsers are allowed to use autocompletion. They do not actually use it for textareas, apparently because it would seldom be useful and could actually be confusing. It is much more probably that a user wants to reuse his address information, entered in single-line text input fields, than some longish text he has entered in, say, a feedback form of some site and now some other site happens to have a comments textarea with the same name.
Thus, all you can do is to set up some autocomplete functionality of your own. (This is what other answers suggest in various ways.) This means that you need to store user input somehow (which is what browsers do for their own autocompletion operations too), e.g. in cookies or in localStorage. This generally means that the functionality works inside a site, on pages using the same technique to implement it, but not across sites.
so the goal is to edit some text with tinymce, persist it and display it in a div using angularJS with the same html, style formating.
I'm using tinymce 3.5.8 with angularUI directive, I've managed to save the content of the wysiwyg in my database (mySQL, TEXT). I'm retrieving it through Spring as a String and send it back to the angularJS app.
I've tried putting a
<div ng-bind-html-unsafe="myModel">
where myModel is defined as
$scope.myModel = Projet.get(getting the json somewhere);
but tags are not interpreted as html, they just print like
<p><span style="color #ff9900;>Texte de test</span></p>.
I've also tried with ngSanitize and ng-bind-html.
html :
<div class="content-swipe-box">
<h3>Contexte</h3>
<div ng-bind-html-unsafe="projet.contexte"></div>
</div>
controller :
$scope.projet = ProjetService.getProject($routeParams.projectId);
Database entry (TEXT)
<p><span style="color: #ff9900;">aaaaa</span></p>
<p> </p>
directive (that's the angularui directive where I've added options):
...
link: function (scope, elm, attrs, ngModel) {
var expression, options, tinyInstance;
// generate an ID if not present
if (!attrs.id) {
attrs.$set('id', 'uiTinymce' + generatedIds++);
}
options = {
skin:"bootstrap",
theme_advanced_disable:"styleselect, anchor",
plugins : "advlist, fullscreen, preview",
theme_advanced_buttons1:"bold, italic, underline, justifyleft, justifycenter, justifyright, justifyfull, formatselect, fontselect, fontsizeselect, forecolor",
theme_advanced_buttons2:"bullist, numlist, outdent, indent, undo, redo, link, unlink, image, cleanup, code, blockquote, hr,removeformat,visualaid,separator,charmap, preview, fullscreen ",
theme_advanced_resizing: true,
theme_advanced_resize_horizontal : false,
force_br_newlines : true,
force_p_newlines : false,
Thank you for your help !
It sounds like you have saved the html to your database as escaped html. If this is what has happened then you will have to unescape it first, you can do that using the technique described in this answer
function htmlDecode(input){
var e = document.createElement('div');
e.innerHTML = input;
return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
}
htmlDecode("<img src='myimage.jpg'>");
// returns "<img src='myimage.jpg'>"
ok so I'm trying to load data and move to another page once I'm clicking on a search button in my index.html
this is my search button
<a href="results.html" data-role="button" data-icon="search"
data-iconpos="notext">search</a>
and while it's loading I want the page to run this function and get data
$(function () { $.getJSON("API.php", {
command: "getBusiness",
orig_lat: myPos.lat,
orig_long: myPos.lon,
distance: 0.05 },
function (result) {
$("#locations").html("");
for (var i = 0; i < result.length; i++) {
$("<a href='business.html?ID=" + result[i].id + "&bsnName=" + "'>
<div>" + result[i].bsnName + " " + (parseInt(result[i].distance * 1000))
"</div></a>").appendTo("#locations");}});});
The page is loading without the DB only when I hit refresh it's showing me the result
I'm not sure what's wrong here, should I not use getJSON?? I have seen people talking about .Ajax() is it the same as getJSON() ?
is there a better idea on how to move to another page and simultaneously grab data from DB to the page your going to load on jquerymobile?
I tried to use the same function using onclick it worked when I gave it a div
the rest of the head
<link rel="stylesheet" href="styles/jquery.mobile.structure-1.1.0.min.css" />
<link rel="stylesheet" href="styles/jquery.mobile.theme-1.1.0.min.css" />
<link rel="stylesheet" href="styles/my.css" />
<script src="scripts/jquery-1.7.2.min.js"></script>
<script src="scripts/jquery.mobile-1.1.0.min.js"></script>
<script src="scripts/cordova-1.8.1.js"></script>
<script>
// Wait for Cordova to load
//
document.addEventListener("deviceready", onDeviceReady, false);
var watchID = null;
var myPos = { lat: 32.0791, lon: 34.8156 };
// Cordova is ready
//
function onDeviceReady() {
// Throw an error if no update is received every 30 seconds
var options = { timeout: 10000 };
watchID = navigator.geolocation.watchPosition(onSuccess, onError, options);
}
// onSuccess Geolocation
//
function onSuccess(position) {
var element = document.getElementById('geolocation');
//myPos.lat=position.coords.latitude;
//myPos.lon=position.coords.longitude;
element.innerHTML = 'Latitude: ' + position.coords.latitude + '<br />' +
'Longitude: ' + position.coords.longitude + '<br />' +
'<hr />' + element.innerHTML;
}
// onError Callback receives a PositionError object
//
function onError(error) {
alert('code: ' + error.code + '\n' +
'message: ' + error.message + '\n');
}
Basically when jQuery mobile loads first or index page it load whole head section (Javascript, CSS etc) and body section. but When the user clicks a link in a jQuery Mobile-driven site, the default behavior of the navigation system is to use that link's href to formulate an Ajax request (instead of allowing the browser's default link behavior of requesting that href with full page load).When that Ajax request goes out, the framework will receive its entire text content, but it will only inject the contents of the response's body element.
There can be multiple solutions to this problem e.g.
The simplest approach when building a jQuery Mobile site is to reference the same set of stylesheets and scripts in the head of every page.
Linking without Ajax by using an attribute data-ajax="false" in your link this attribute will load the next page without ajax and animation so both head and body section would load.
If you need to load in specific scripts or styles for a particular page, It is recommended binding logic to the pageInit e.g. "#aboutPage" is id="aboutPage" attribute .
$( document ).delegate("#aboutPage", "pageinit", function() {
//you can place your getJson script here. that will execute when page loads
alert('A page with an ID of "aboutPage" was just created by jQuery Mobile!');
});
So in your case better solution is to bind your ajax call or other particuler script with pageinit event.
You can get help from these pages of jQuery Mobile documentation.
http://jquerymobile.com/demos/1.1.0/docs/pages/page-links.html
http://jquerymobile.com/demos/1.1.0/docs/pages/page-scripting.html
I am struggling to figure out how to do this with MVC,
I have an entity framework object that has a comma separated list from the db, (can't change the fact that its a horrible csl in the db). I can easily display the list and let them edit it manually. This is rather error prone and would like to split them up and display a list of them in the view. Then allow the user to click a link / button and have them removed from the string and db and the page refreshed to reflect this.
My first thought was to use JQuery to do a ajax json post to do a delete for each item the click an #Html.ActionLink for. I could get it to do the async post back and it would delete the item and would send back a string representing the new string list which I could update the UL with. The second time they clicked a link it would give me a 404, the script I used is:
<script type="text/javascript">
$(document).ready(function () {
$('.viewSeasonsLink').click(function () {
var data =
{
item: $(this).parents('li').first().find('.flagName').text(),
deploymentId: #Model.Id
};
$.post(this.href, data, function (result) {
var list = $("#testme");
list.empty();
var items = result.split(",");
$(items).each(function(index) {
// /* var link = '"' + #Html.ActionLink("Remove", "RemoveItemFromList", "Deployment", null, new { #class = "viewSeasonsLink" }) + '"'; */
var link = '<a class="viewSeasonsLink" href="/SAMSite/Deployment/RemoveItemFromList">Remove</a>';
list.append('<li><span class="flagName">' + items[index] + '</span> - ' + link + ' </li>');
/* list.append('<li><span class="flagName">' + items[index] + '</span> - ' + '\'' + #Html.ActionLink("Remove", "RemoveItemFromList", "Deployment", null, new { #class = "viewSeasonsLink" }) + '\'</li>'); */
});
}, "json");
return false;
});
});
</script>
I could not get the action link to work with the jquery script, so tried hard coding it, still not success.
I then thought I would just try and do a simple actionlink back to a method to remove it and return the normal view, again this posts and will update the db, but will not refresh the webpage at all.
<ul id="testme2">
#foreach (string flag in ViewBag.FeatureFlags)
{
<li><span class="flagName">#flag</span> - #Html.ActionLink("Remove", "RemoveItemFromListTest", "Deployment", null, new { #class = "viewSeasonsLink" })</li>
}
</ul>
public ActionResult RemoveItemFromListTest(string item, int deploymentId)
{
Deployment deployment = db.Deployments.Single(d => d.Id == deploymentId);
ViewBag.CustomerId = new SelectList(db.Customers, "Id", "Name", deployment.CustomerId);
List<string> featureFlags = deployment.FeatureFlags.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
featureFlags.Remove(item);
deployment.FeatureFlags = ConvertBackToCommaList(featureFlags);
ViewBag.FeatureFlags = featureFlags;
//db.SaveChanges();
return View("Edit", deployment);
}
EDIT
released I was being a bit daft at one point:
The second test to get it to do a full post back and do the update was still getting caught by the jquery, (also was not passing in the values). I changed the line to this:
<li><span class="flagName">#flag</span> - #Html.ActionLink("Remove", "RemoveItemFromListTest", "Deployment", new { item = #flag, deploymentId = Model.Id }, null)</li>
which does work, but is a bit naff, it would mean any changes made to the form before the remove link clicked would be lost.
I think I see two issues. One is the initial .Post on the viewSeasonsList click event. You are posting back to the Action that loaded the page, not the Action that will handle the delete. I doesn't seem to me that they would be the same Action base on the approach you described.
var url = '/SAMSite/Deployment/RemoveItemFromList';
then
$.post(url, data, function (result) {
Second, in the Ajax response, when you are rebuilding the list, you are including an href attribute for the links. Why? you are not navigating with those links, you are initiating an Ajax request, which has already been set up.
var link = '<a class="viewSeasonsLink">Remove</a>';
ultimately I had one main problem with the jquery solution. When I added a new LI element it was not being hooked up to the ajax call as this was just happening at document.ready. I now replaced the simple .click with a delegate that will also hook up all elements that are added after the ready event, credit to this page for help with it:
$('#featureflaglist').delegate('.removeflaglink', 'click', RemoveFlagFromList);
I'm trying to insert a placeholder in html code that will be replaced later on dynamically. So far I managed to get the code inserted, and TinyMCE recognizes the tag, but when I try to append an id attribute to it, the attribute gets removed for an unknown reason. I tried most of the additional options, but none seem to work.
Current config:
extended_valid_elements : "module[id]",
valid_children : "module[img]",
custom_elements : "module",
The code to create the button (and subsequently insert the code):
setup : function(ed) {
// Add a custom button
ed.addButton("module", {
title : "Module",
image : "images/app-x-php-icon.png",
onclick : function() {
ed.focus();
var options = document.getElementById('rendermcemods').innerHTML+"";
var optionList = options.split('|');
var name=prompt("Please enter module name out of: "+options,optionList[0]);
for(var i=0;i<optionList.length;i++){
if(optionList[i] == name){
var patt=new RegExp('<module id="'+name+'">.*</module>','ig');
var content = '<module id="'+name+'"><img src="images/app-x-php-icon.png" /></module>';
//alert(content);
if(! patt.test(ed.getContent())){
ed.execCommand('mceInsertContent', false,content);
}
}
}
}
});
}
As you might notice, there's an alert before the insert, which I used to verify that the content is right...
When use the button to insert the code and then view the html, this is what I get:
<module><img src=images/app-x-php-icon.png" alt="" /></module>
Would anyone know how to fix this?
Update:
full config settings for tinyMCE:
// General options
mode : "none",
theme : "advanced",
plugins : "autolink,lists,spellchecker,pagebreak,style,layer,table,\n\
save,advhr,advimage,advlink,emotions,iespell,inlinepopups,\n\
insertdatetime,media,searchreplace,print,contextmenu,paste,\n\
directionality,fullscreen,noneditable,visualchars,\n\
nonbreaking,xhtmlxtras",
// Theme options
theme_advanced_buttons1 : "fullscreen,help,|,bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,styleselect,formatselect,fontselect,fontsizeselect,|,module",
theme_advanced_buttons2 : "cut,copy,paste,pastetext,pasteword,|,search,replace,|,bullist,numlist,|,outdent,indent,blockquote,|,undo,redo,|,link,unlink,anchor,image,cleanup,code,|,insertdate,inserttime,|,forecolor,backcolor",
theme_advanced_buttons3 : "tablecontrols,|,hr,removeformat,visualaid,|,sub,sup,|,charmap,emotions,iespell,media,advhr,|,ltr,rtl,|,spellchecker,|,cite,abbr,acronym,del,ins,attribs,|,visualchars,nonbreaking,blockquote,|,insertfile,insertimage",
theme_advanced_toolbar_location : "top",
theme_advanced_toolbar_align : "left",
theme_advanced_statusbar_location : "bottom",
theme_advanced_resizing : true,
// Skin options
skin : "o2k7",
skin_variant : "silver",
document_base_url : "http://www.example.com",
content_css : "content.css",
extended_valid_elements : "module[id]",
valid_children : "module[img]",
/*custom_elements : "module", */
// Drop lists for link/image/media/template dialogs
external_link_list_url : "js/generateList.php?A=link",
external_image_list_url : "js/generateList.php?A=image",
media_external_list_url : "js/generateList.php?A=media",
setup : function(ed) {
// Add a custom button
ed.addButton("module", {
title : "Module",
image : "images/app-x-php-icon.png",
onclick : function() {
ed.focus();
var options = document.getElementById('rendermcemods').innerHTML+"";
var optionList = options.split('|');
var name=prompt("Please enter module name out of: "+options,optionList[0]);
for(var i=0;i<optionList.length;i++){
if(optionList[i] == name){
var patt=new RegExp('<module id="'+name+'">.*</module>','ig');
var content = '<module id="'+name+'"><img src="images/app-x-php-icon.png" /></module>';
//alert(content);
if(! patt.test(ed.getContent())){
ed.execCommand('mceInsertContent', false,content);
}
}
}
}
});
}
Another update: It might be interesting (and hopefully help to solve) to know that the id attribute isn't removed when tinyMCE is loaded and it already is in there, and a clean-up on existing code with the attribute doesn't remove it either.
I would put module to the valid_elements instead of the extended_valid_elements/custom_elements. The extended_valid_elements do sometimes behave strange.
My own config then looks like this (you will need to enlarge your own valid_elements and valid_children settings (if not used in your custom tinymce config you will have to use the defaults (can be found at the moxiecode website))):
// The valid_elements option defines which elements will remain in the edited text when the editor saves.
valid_elements: "#[id|class|title|style|onmouseover]," +
"module," +
"a[name|href|target|title|alt]," +
"#p,blockquote,-ol,-ul,-li,br,img[src|height|width],-sub,-sup,-b,-i,-u," +
"-span[data-mce-type],hr",
valid_children: "body[p|ol|ul|hr]" +
"module[img]" +
",p[a|span|b|i|u|sup|sub|img|hr|#text|blockquote]" +
",span[a|b|i|u|sup|sub|img|#text|blockquote]" +
",a[span|b|i|u|sup|sub|img|#text|blockquote]" +
",b[span|a|i|u|sup|sub|img|#text|blockquote]" +
",i[span|a|b|u|sup|sub|img|#text|blockquote]" +
",sup[span|a|i|b|u|sub|img|#text]" +
",sub[span|a|i|b|u|sup|img|#text]" +
",li[span|a|b|i|u|sup|sub|img|ol|ul|#text]" +
",ol[li]" +
",ul[li]",
The solution I ended up using was modifying the blockElementsMap and the transitional map taht are in the source code. That seemed to be the only way to get the custom tag recognized as 'blocklevel' element, as well as being able to add it exactly like I want in the code for later processing.