highlightselection function in rangy overrides previous getselection - rangy

I'm using Rangy for highlighting text and stumbled upon a problem when calling the highlightSelection function.
highlightSelection: function(className, options) {
var converter = this.converter;
var classApplier = className ? this.classAppliers[className] : false;
options = createOptions(options, {
containerElementId: null,
selection: api.getSelection(this.doc),
exclusive: true
});
var containerElementId = options.containerElementId;
var exclusive = options.exclusive;
var selection = selection || options.selection;
var doc = selection.win.document;
var containerElement = getContainerElement(doc, containerElementId);
if (!classApplier && className !== false) {
throw new Error("No class applier found for class '" + className + "'");
}
// Store the existing selection as character ranges
var serializedSelection = converter.serializeSelection(selection, containerElement);
// Create an array of selected character ranges
var selCharRanges = [];
forEach(serializedSelection, function(rangeInfo) {
selCharRanges.push( CharacterRange.fromCharacterRange(rangeInfo.characterRange) );
});
var newHighlights = this.highlightCharacterRanges(className, selCharRanges, {
containerElementId: containerElementId,
exclusive: exclusive
});
// Restore selection
converter.restoreSelection(selection, serializedSelection, containerElement);
return newHighlights;
},
It looks like the selection object is being overridden with another call to getSelection().
What's the best way to stop it from doing that?

After doing further research, I came a cross an update by the creator of Rangy, to specifically address this issue. So,
Download the latest version of the files and make sure this is what you have in rangy-highlighter.js file under highlightSelection: function:
options = createOptions(options, {
containerElementId: null,
exclusive: true
});
var containerElementId = options.containerElementId;
var exclusive = options.exclusive;
var selection = options.selection || api.getSelection(this.doc);
var doc = selection.win.document;
var containerElement = getContainerElement(doc, containerElementId);
call the highlightSelection function like:
'highlighter.highlightSelection("highlight", {selection: sel});'
So you're setting your selection key with the value sel. 'selection' is just the name of the key expected by this function (read the github docs for more options and information) and sel should be the object your are trying to highlight and be called prior like:
'sel = rangy.getSelection();'

I am building a custom tool tip when someone highlights text, and I came across this issue. The way I solved it, was by creating a global variable range, and setting it to rangy.getSelection().getRangeAt(0). This will get you the range object for the selection, afterwards you can set the selection back to your saved value like this: rangy.getSelection().addRange(this.range)

Related

Appmaker Query on Calculated Records

I'm sure I'm doing something wrong... but every time I query on a calculated datasource, I get the error "cannot handle returning cyclic object."
Here's the gist:
I have a calculated model that fetches a user's google contacts and places the full name field into a table on the UI. The goal is to have a separate text box that can be used to search the full name field and then repopulate the table on the same page with the results of the search, similar to how google contacts search behavior works. The on value change event of the text box sends the textbox value to this server script:
function searchContacts (sq) {
var ds = app.models.Contacts.newQuery();
ds.filters.FullName._contains = sq;
var results = ds.run();
return results;
}
But every time I get the cyclic object error when the values are returned from that function. The error actually fires when the query run command (ds.run) is executed.
I've tried querying the datasource as well, but I've read somewhere that you can't query the datasource of a calculated model because it doesn't exist, so you have to query the model.
Any help would be much appreciated.
From your question it is not 100% clear, what you are trying to do. In case you are actually using Calculated Model, then your Server Script Query should look like this:
var sq = query.parameters.SearchQuery;
var contactsQuery = app.models.Contacts.newQuery();
contactsQuery.filters.FullName._contains = sq;
var contacts = ds.run();
var results = contacts.map(function(contact) {
var calcRecord = app.MyCalcModel.newRecord();
calcRecord.Name = contact.FullName;
return calcRecord;
});
return results;
Note, that you cannot return objects of arbitrary type from Server Script Query, only of type of this particular Calculated Model.
But from some parts of your description and error text if feels like you are trying to load records with async serever call using google.scritp.run. In this case you cannot return App Maker records(App Script doesn't allow this) and you need to map them to simple JSON objects.
I don't think I was super-clear on my original post.
I have a calculated model that is all of the user's contacts from Google Contacts (full name, email, mobile, etc...) On the UI I have a list widget that's populated with all of the Full Name fields and above the list widget a text input that's used to search the list widget. So the search text box's on input change event sends a request to query the Full Names, similar to how Google Contact's search feature works.
Screen Shot
It appears that App Maker doesn't let you query calculated models, so I have this workaround - unless someone comes up with something better:
This is the onInputChange handler for the search text box:
sq = app.pages.SelectClient.descendants.TextBox1.value;
app.datasources.SearchContacts.query.parameters.Name = sq;
app.datasources.SearchContacts.load();
This is the Server Script Code (thanks to #Pavel Shkleinik for the heads up):
var sq = query.parameters.Name;
if (sq !== null) {
return getContactsbyName(sq);
} else {
return getContacts();
}
And the server code with no query:
function getContacts() {
var results = [];
var contacts = ContactsApp.getContacts();
contacts.forEach(function(item) {
var contact = app.models.Contacts.newRecord();
contact.FullName = item.getFullName();
var emails = item.getEmails(ContactsApp.Field.WORK_EMAIL);
if (emails.length > 0) {
contact.PrimaryEmail = emails[0].getAddress();
}
contact.LastName = item.getFamilyName();
contact.FirstName = item.getGivenName();
var phones = item.getPhones(ContactsApp.Field.MOBILE_PHONE);
if (phones.length > 0) {
contact.Mobile = phones[0].getPhoneNumber();
}
var addresses = item.getAddresses(ContactsApp.Field.WORK_ADDRESS);
if (addresses.length > 0) {
contact.Address = addresses[0].getAddress();
}
results.push(contact);
results.sort();
});
return results;
}
And with the query:
function getContactsbyName(sq) {
var results = [];
var contacts = ContactsApp.getContactsByName(sq);
contacts.forEach(function(item) {
var contact = app.models.Contacts.newRecord();
contact.FullName = item.getFullName();
var emails = item.getEmails(ContactsApp.Field.WORK_EMAIL);
if (emails.length > 0) {
contact.PrimaryEmail = emails[0].getAddress();
}
contact.LastName = item.getFamilyName();
contact.FirstName = item.getGivenName();
var phones = item.getPhones(ContactsApp.Field.MOBILE_PHONE);
if (phones.length > 0) {
contact.Mobile = phones[0].getPhoneNumber();
}
var addresses = item.getAddresses(ContactsApp.Field.WORK_ADDRESS);
if (addresses.length > 0) {
contact.Address = addresses[0].getAddress();
}
results.push(contact);
results.sort();
});
return results;
}
This way, the list populates with all of the names when there's no search query present, and then re-populates with the search query results as needed.
The only issue is that the call to the Google Contacts App to populate the Calculated Model is sometimes very slow.

How to define a persistent property in JXA

In AppleScript I would write
property foo: "value"
and the value would be saved between runs. How can I do this in Javascript for Automation?
JavaScript for Automation doesn't have a direct parallel for the persistent property (and global value) mechanism of AS, but it does have JSON.stringify() and JSON.parse(), which work well for simple serialisation and retrieval of state.
Perhaps something broadly like:
(function () {
'use strict';
var a = Application.currentApplication(),
sa = (a.includeStandardAdditions = true, a),
strPath = sa.pathTo('desktop').toString() + '/persist.json';
// INITIALISE WITH RECOVERED VALUE || DEFAULT
var jsonPersist = $.NSString.stringWithContentsOfFile(strPath).js || null,
persistent = jsonPersist && JSON.parse(jsonPersist) || {
name: 'perfume',
value: 'vanilla'
};
/*********************************************************/
// ... MAIN CODE ...
// recovered or default value
sa.displayNotification(persistent.value);
// mutated value
persistent.value = "Five Spice"
/*********************************************************/
// WRAP UP - SERIALISE TO JSON FOR NEXT SESSION
return $.NSString.alloc.initWithUTF8String(
JSON.stringify(persistent)
).writeToFileAtomically(strPath, true);
})();
(A fuller example here: https://github.com/dtinth/JXA-Cookbook/wiki/Examples#properties-which-persist-between-script-runs )
Or, for simple key-value pairs rather than arbitrary data structures, see:
https://stackoverflow.com/a/31902220/1800086 on JXA support for writing and reading .plist

IndexedDB wildcard at start and end of searchterm

This topic explains how to use wildcards ad the end of the searchterm using IndexedDB.
I am looking for a way to add a wildcard at the end AND at the start of the searchterm.
In SQL it would be: LIKE '%SearchTerm%'.
How can I achieve this with IndexedDB? Here is my code:
function getMaterials() {
var materialNumber = $("#input").val();
var transaction = db.transaction(["materials"]);
var objectStore = transaction.objectStore("materials");
var request = objectStore.openCursor(IDBKeyRange.bound(materialNumber, materialNumber + '\uffff'), 'prev');
$("#output").find("tr:gt(0)").remove();
request.onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
var newRow = '<tr><td>'+ cursor.value.materialNumber +'</td>'+
'<td>'+ cursor.value.description +'</td>'+
'<td>'+ cursor.value.pieces +'</td>'+
'<td>'+ cursor.value.price +'</td></tr>';
$('#output').append(newRow);
cursor.continue();
}
};
};
EDIT:
I could achieve this by letting indexDB return all rows and then narrow down in JavaScript. But there must be a better approach in terms of performance.
if (cursor.value.materialNumber.indexOf(materialNumber) != -1){
//add result...
}
This was not how idb was intended to be used. If you want text searching, parse text into tokens, store the tokens, use an index on the tokens, and do lookups on the index to get a pointer to the full text.

Google maps Autocomplete: output only address without country and city

I use Places library to autocomplete address input. Search is limited to only one city, and I get output like this:
"Rossiya, Moskva, Leninskiy prospekt 28"
How to hide "Rossiya, Moskva"? ...
My query:
function() {
// Search bounds
var p1 = new google.maps.LatLng(54.686534, 35.463867);
var p2 = new google.maps.LatLng(56.926993, 39.506836);
self.options = {
bounds : new google.maps.LatLngBounds(p1, p2),
componentRestrictions: {country: 'ru'},
};
var elements = document.querySelectorAll('.address');
for ( var i = 0; i < elements.length; i++) {
var autocomplete = new google.maps.places.Autocomplete(elements[i],
self.options);
}
You can but you have to replace the value of the input field in two places.
Example:
var autocomplete = new google.maps.places.Autocomplete(input, placesOptions);
var input = document.getElementById('searchTextField');
inside the 'place_changed' event you need to do the following:
placeResult = autocomplete.getPlace();
//This will get only the address
input.value = placeResult.name;
This will change the value in the searchtextfield to the street address.
The second place is a bit tricky:
input.addEventListener('blur', function(){
// timeoutfunction allows to force the autocomplete field to only display the street name.
if(placeResult){ setTimeout(function(){ input.value = placeResult.name; }, 1); } });
The reason why we have to do this is because if you only add the event listener for blur, google places will populate the input field with the full address, so you have to 'wait' for google to update and then force your change by waiting some miliseconds.
Try it without the setTimeout function and you will see what I mean.
EDIT
You can't. I had it the other way around, that you were just looking for a city. There is no way to only print out the street name (I'm assuming that's a street name) from the address component.
OPPOSITE OF WHAT WAS ASKED
From the docs:
the (cities) type collection instructs the Place service to return results that match either locality or administrative_area3.
var input = document.getElementById('searchTextField');
var options = {
bounds: defaultBounds,
types: ['(cities)']
};
autocomplete = new google.maps.places.Autocomplete(input, options);
in result u have hash and from it u can get part what u want:
google.maps.event.addListener(autocomplete, 'place_changed', function() {
var place = autocomplete.getPlace();
now from "place" u can get it
place.geometry.location.lat()
and for address
place.address_components[0] or place.address_components[1] ...
depends on what u want to get
I had a very similar problem which indeed was solvable. This in an Angular 2 project but it should be applicable elsewhere as well. I filter my results for establishments, and wanted to show only the name and hide the address part of the result. This did the trick for me, a function executing once you select a suggestion:
getAddress(place: Object) {
this.zone.run(() => {
this.establishment = place['name'];
});
where zone is an NgZone component injected in the constructor and this.establishment is the variable tied to [(NgModel)] in the input field.
Inside place_changed set a timeout function:
var streetString = place.address_components[0] or place.address_components[1];
window.setTimeout(function() {
$('input').val(streetString);
}, 200);
This solution worked for me.

jQuery - can't set css propery in ajax callback function

I'm trying to get an jquery ajax callback function to update the background colour of a table cell, but I can't get it to work.
I have the following code (which produces no errors in Firebug):
$(".tariffdate").click(function () {
var property_id = $('#property_id').attr("value");
var tariff_id = $('#tariff_id').attr("value");
var tariff_date = $(this).attr("id");
$.post("/admin/properties/my_properties/booking/edit/*", { property_id: property_id, tariff_id: tariff_id, tariff_date: tariff_date },
function(data){
var bgcol = '#' + data;
$(this).css('background-color',bgcol);
alert("Color Me: " + bgcol);
});
I've added the alert just to confirm I'm getting the expected data back (a 6 digit hexadecimal code), and I am - but the background of my table cell stubbornly refuses to change.
All the table cells have the class .tariffdate but also have individual ID.
As a test, I tried creating a hover function for that class:
$(".tariffdate").hover(function () {
$(this).css('background-color','#ff0000');
});
The above works fine - so I'm really confused as to why my callback function is not functioning. Any ideas?
In the AJAX completed handler the instance of this is changed to the ajax object. You'll need to save the instance of this to an object and use that object. For example:
$(".tariffdate").click(function () {
var property_id = $('#property_id').attr("value");
var tariff_id = $('#tariff_id').attr("value");
var tariff_date = $(this).attr("id");
var tariff = $(this);
$.post("/admin/properties/my_properties/booking/edit/*",
{ property_id: property_id, tariff_id: tariff_id, tariff_date: tariff_date },
function(data) {
var bgcol = '#' + data;
tariff.css('background-color',bgcol);
alert("Color Me: " + bgcol);
}
);
});
Check what the "this" variable is in you ajax callback function. I suspect that it's not referring to .tariffdate

Resources