When I design interfaces, I can't be bothered to create believable dummy text. At the same time, however, I don't want to just copy and paste the same exact data over and over, because then the interface doesn't look terribly realistic.
I have generated a Master JSON Schema that contains the most common types of data I use. I'd like to be able to do something like this when I'm writing HTML:
<ul>
<li>{first_name}</li>
<li>{first_name}</li>
...
<li>{first_name}</li>
</ul>
OR
<ul>
<li data="{first_name}"></li>
<li data="{first_name}"></li>
...
<li data="{first_name}"></li>
</ul>
...whereby every instance of {first_name} is replaced with a random first name from my JSON file. Likewise for any other variable I have in there ( last_name, email, address, country, sentence, etc... )
Is there a way to do this without PHP in something like jQuery? I imagine it'd have to be something like:
foreach(var) {
return randomData(var);
}
Ideally I'd have a very simple, generalized function that would comb through the UI looking for any and all tags and replace each one with a random piece of data from the master schema JSON file.
Click below to see solution I came up with thanks to Billy's help:http://jsfiddle.net/JonMoore/5Lcfz838/2
between http://chancejs.com/ and http://handlebarsjs.com/ you can generate lots of repeatable, seeded random data structures...
// get references to DOM elements
var tplElm = document.getElementById('template');
var tgtElm = document.getElementById('target');
// compile handlebars template
var template = Handlebars.compile(tplElm.innerText);
// initialise chance with seed
// change the seed to change the output data
var chance = new Chance(1234567890);
// create array of random data
var people = [];
for(var i=0;i<10;i++){
people.push({
first_name: chance.name()
});
}
// apply data to template, and insert into page
tgtElm.innerHTML = template({
people: people
});
<!-- load dependencies -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/chance/0.5.6/chance.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/3.0.3/handlebars.min.js"></script>
<!-- define template -->
<script id="template" type="text/template">
<ul>
{{#people}}
<li>{{first_name}}</li>
{{/people}}
</ul>
</script>
<!-- placeholder for output -->
<div id="target"></div>
Something like this will give you what you want:
var json = [{ "first_name": "bob"}, {"first_name": "sam"}, {"first_name": "bill"}];
var randomnum = Math.floor((Math.random() * 3));
console.log(json[randomnum].first_name);
You can access this data using AJAX and then get elements using Math.random.
Then, with the help of jQuery you can dynamically generate li items.
Let's suppose you have a div element like <div id="anyId"></div> where you will put your ul and lis.
function getElementsFromMasterSchema(count, callback) {
var items = [];
var usedIds = {};
$("h3").text("Request sent...Loading...");
timer = setInterval(function() {
$("h3").text("Loading..." + (time++) + " seconds passed.");
}, 1000);
$.ajax({
url: "https://www.mockaroo.com/37dcc3b0/download?count=100&key=37b5a7c0",
method: "GET"
}).done(function(dt) {
var length = dt.length;
while (items.length < count) {
var randomItem = dt[Math.round(Math.random() * length)];
if (usedIds[randomItem.id] !== true) {
usedIds[randomItem.id] = true;
items.push(randomItem);
}
}
callback(items);
});
}
getElementsFromMasterSchema(10, function(result) {
var ul = $("<ul/>");
for (var i = 0; i < result.length; i++) {
$("<li/>").text(result.first_name).appendTo(ul);
}
$("#anyId").append(ul);
});
Note that you need to make requests from the same domain or set Access-Control-Allow-Origin header in order to make cross-domain requests.
However, this method is working slowly. It takes from 5 to 20 seconds to load this file. Loading a large file from a server and using only some of data is a bad approach.
You definitely need to implement this algorithm on a server side. Something like SQL SELECT TOP n FROM x ORDER BY newid() (SELECT * FROM x ORDER BY RAND() LIMIT n).
Related
What I'm trying to do is, dynamically create clickable links from user provided textarea data, first being saved to the local machine using localStorage. User data is provided in the following format:
a|aa,
c|cc,
e|ee,
a,c,e are the labels for the links
aa,cc,ee are the links themselves
example of final output:
<a href="aa" />a</a>
<a href="cc" />c</a>
<a href="ee" />e</a>
Step 1: Save data to localStorage in a format that will later be used to create 2 separate arrays.
This is what I've done so far for step 1 (not sure it's correct)
// remove white space and carriage returns
textToSave = textToSave.replace(/(\r\n|\n|\r)/g,"").trim();
// replace | from user data with =>
var textToSaveArrayFormat = textToSave.replace(/(\|)/g, "=>");
// store data to localSorage
localStorage.setItem("appLinkConfig" ,JSON.stringify(textToSaveArrayFormat));
Step 2: Retrieve data from localStorage and create 2 arrays from that data
URLLabels = ["a", "b", "c"];
URLOptions = ["aa", "bb", "cc"];
For step 2 I start with
// get local storage app link config data
var appLinksObj = JSON.parse(localStorage.getItem("appLinkConfig"));
console.log(appLinksObj);
which returns
a=>aa,c=>cc,e=>ee,
From this I need to create my 2 arrays and this is where I'm stuck.
Step 3: I'm currently doing this with hardcoded arrays in my script, but would like to do it with the array data created in step 1 and 2.
// Object created
var obj = {};
// Using loop to insert key
// URLOptions in Object
for(var i = 0; i < URLLabels.length; i++){
obj[URLLabels[i]] = URLOptions[i];
}
// Printing object
for (var URLLabels of Object.keys(obj)) {
lnk.innerHTML += "<a href=\'" + URLLabels + "' target\='" + hotDeck + "'\>" + obj[URLLabels] + "</a>";
}
hotDeck is a flag I'm using to target one of two frames (returns the ID of those frames) contained in the page.
Thanks in advance for your help - please be gentle, I've been away from coding for a long time due to illness and coding again now to help my recovery. I'd be very grateful to solve this problem.
I assume this is what you are expecting. try the snippet on the console as i am not able to aceess localStorage property in the code snippet
var input = 'a|aa, c|cc, e|ee';
var output = input.split(',').map(str => str.split('|'));
window.localStorage.setItem('appLinkConfig', JSON.stringify(output));
var config = JSON.parse(window.localStorage.getItem('appLinkConfig'));
var result = config.map(([text, href]) => `${text.trim()}`).join(' ')
console.log(result);
I have got very large model list in view and i would like to send the list back to controller using ajax query. I have tried to send the whole model list back but since the model is too large, it exceeds the json maxlength specified within web.config. The encode method works for smaller list though.
var jsonString = #Html.Raw(Json.Encode(Model.modelName_small));
Only way that i can vision it to work is but filtering the large model list into smaller list using javascript (similar to a 'Where' SQL statement). My script are as follows (razor):
<script type="text/javascript" language="javascript">
function functionName(input1_decimal) {
var smallerList = new Array();
#foreach (var item in Model.modelName)
{
//input1_decimal should be within a certain range
#:if (input1_decimal - 0.1 <= #item.Property1) && (#item.Property1 <= input1_decimal + 0.1)
{
#:smallerList.push("#item");
}
}
//convert smallerList to json and send it to controller
}
<script>
it seems quite straight forward but I just can not get it to work. Might be something quite trivial. I have also tried:
var smallerList= Model.modelName.Where(x => (input1_decimal - 0.1 <= x.Property1) && (x.Property1 <= input1_decimal + 0.1));
Similarly, i have also tried
var smallerList = Model.modelName.filter(function (item) {
return (input1_decimal - 0.1 <= item.Property1) && (item.Property1<= input1_decimal + 0.1)
});
Thank you for your patience. i hope i have explained it clearly as to what i am trying to achieve. I am not a developer. Programming just for fun and self education.
Are you modifying data on the view ? If so, one other approach is to post only modified data to the controller in order to minimized the json string length and retrieve the rest of the data directly in the controller.
instead of editing jsonmaxlength field within web.config, I assigned MaxJsonLength to Int32.MaxValue. Created a list and assigned properties to model properties and serialise into Json object list. Then i filtered the list using $.grep function. Finally, I was able to send objJsonSmallList back to controller... Happy days :)
#{
var js = new System.Web.Script.Serialization.JavaScriptSerializer();
js.MaxJsonLength = Int32.MaxValue;
//Create a list and assigning all the properties of the model
var data = Model.model_Name.Select(x => new
{
propName1 = x.property1,
propName2 = x.property2,
...
propNameN = x.propertyN
});
//serialize collection of anonymous objects
string strArr = js.Serialize(data);
}
var objJsonBigList = JSON.parse('#strArr'.replace(/"/g, '"'));
//small Filtered list send to controller via Ajax
var objJsonSmallList = $.grep(objJsonBigList, function (n) {
return ((input1_decimal- 0.1 <= n.Prop) && (n.Prop <= input1_decimal + 0.1))
});
I am making a web app for a Spreadsheet for a sidebar.
Trying to handle an event listener that implements these conditions:
when checking checkboxes (which have correspondent names and Calendar links for that names)
and picking 2 dates
and then clicking the button
After it's all done a function is called from backend which gets events for that names and records them to a sheet.
I can't get the values for names and calendars in a loop. And not sure what's the good way to put them. I tried to handle it in different ways - no luck.
I eventually narrowed all issues to these 2 parts:
1) how to load data to a sidebar in a best way.
2) how to loop through that data after user interacted with these elements and to get values (depends on the part 1).
I would really appreciate if someone help me a bit more on it (some rather simple solutions).
Here's the variant with scriptlets for using GAS in an html file:
<? for (var i = 0; i < loopNamesForSidebar().names.length; i++) { ?>
<label>
<input type="checkbox" class="filled-in check" checked="checked" />
<span>
<div class="collection">
<a href=" <?= loopNamesForSidebar().calendars[i] ?>" class="collection-item" >
<?= loopNamesForSidebar().names[i] ?>
</a>
</div>
</span>
<? } ?>
</label>
loopNamesForSidebar() is a backend function which loops names and calendars that go to the sidebar. Every time I open the sidebar this data refreshes. I don't have it used in my front-end part.
Here's a Javascript code in an html file:
//import jobs from calendars to sheet
function importJobs() {
//getting all checkboxes
var allCheckboxes = document.getElementsByClassName("check")
//getting inputs of start and end dates
var startDate = document.getElementById("startDate").value
var endDate = document.getElementById("endDate").value
var chosenNames = []
var calendars = []
//looping all checkboxes
for (var i = 0; i < allCheckboxes.length; i++) {
//getting value of each checkbox
var checkbox = allCheckboxes[i].checked;
//if checkbox is checked
if (checkbox == true) {
//getting correspondant employee name
var name = loopNamesForSidebar().names[i]
//and push it to an array
chosenNames.push(name)
//getting correspondant employee calendar
var cal = loopNamesForSidebar().calendars[i]
calendars.push(cal)
} else {
continue;
}
};
//push names and cals to object
var employees = {
names: chosenNames,
cals: calendars
}
//call function to get calendar events
google.script.run.getEvents(employees, startDate, endDate)
};
Per Best Practices, you should load any API / ___ Service call output asynchronously. I would only use template evaluation for trivially computed / retrieved data, e.g. from PropertiesService, CacheService, static definitions, or simple lookups based on input parameters (i.e. querystring / payload data for doGet and doPost trigger functions). If it takes longer than a quarter second on average, it's too slow for synchronous usage.
so:
function templateOK(param) {
const val = PropertiesService.getScriptProperties().getProperty(param);
return val ? JSON.parse(val) : [
"name 1", "name 2", "name 3"
];
}
function shouldRunAsync(param) {
const sheet = param ? SpreadsheetApp.getActive().getSheetByName(param) : null;
return sheet ? sheet.getDataRange().getValues() : [];
}
Assuming you've set up the other parts of your GS and HTML files appropriately, one of the .html's <script> tags may look something like this:
$(document).ready(() => loadServerData()); // jQuery
function loadServerData() {
const TASK = google.script.run.withSuccessHandler(useNames); // has implicit failure handler of `console`
// Schedule this to be run every so often
const intervalMS = 10 * 60 * 1000; // 10 minutes
setInterval(sheetName => TASK.shouldRunAsync(sheetName), intervalMS, "Names");
// Invoke promptly too.
TASK.shouldRunAsync("Names");
console.log(new Date(), "Set schedule & invoked server function");
}
function useNames(serverValue, userObject) {
console.log(new Date(), "Got Value from server", serverValue);
// use the return value to do stuff, e.g.
const cbDiv = $("id of div containing checkboxes"); // jQuery
/** could add code here to read existing checkbox data, and
use that to maintain checked/unchecked state throughout loads */
cbDiv.innerHTML = serverValue.map((name, idx) => `<p id="${name}">${idx + 1}: ${name}</p>`).join("");
}
As always, make sure you are intimately familiar with the serializable data types and the Client-Server communication model: https://developers.google.com/apps-script/guides/html/communication
Other refs
Element#innerHTML
Array#map
setInterval
Arrow functions
Template literals
I'm trying to implement an input field with an autocomplete feature. I'd be using Google Books API to autocomplete names of books based on the keyword that the user enters in the input text field. I'd be using Django as my framework to implement this feature.
This is what I have been able to do so far:
JS
$( document ).ready(function()
{
$("#id_book_name").on("change paste keyup", function()
{
var app_url = document.location.origin;
var book_name = $('#id_book_name').val();
var url = app_url+'/book-search/';
if(book_name.length > 4)
{
var data = {
'book_name': book_name,
'csrfmiddlewaretoken': document.getElementsByName('csrfmiddlewaretoken')[0].value,
};
console.log(data);
$.post(url, data).done(function(result)
{
for(var book_title of result)
{
console.log(book_title);
}
console.log(result);
}).fail(function(error)
{
console.log(error)
});
return false;
}
});
});
Here, #id_book_name is the id of my input text field. As soon as the length of the keyword entered by the user exceeds 4, I'm sending a POST request to /book-search which is mapped to the following Python function where I hit Google Books API's endpoint and return the book titles in a specific JSON format:
def book_search(request):
book_results = {'titles':[]}
key = 'XXXXXXX'
url = 'https://www.googleapis.com/books/v1/volumes?q=' + request.POST['book_name'] + '&maxResults=5&key=' + key
result = requests.get(url)
json_result = json.loads(result.text)
if 'items' in json_result:
for e in json_result['items']:
if 'industryIdentifiers' in e['volumeInfo']:
isbn = ''
for identifier in e['volumeInfo']['industryIdentifiers']:
isbn = identifier['identifier'] if (identifier['type'] == 'ISBN_10') else isbn
if 'subtitle' in e['volumeInfo']:
book_results['titles'].append(e['volumeInfo']['title'] + ' - '
+ e['volumeInfo']['subtitle'] + ' (' + isbn + ')')
else:
book_results['titles'].append(e['volumeInfo']['title'] + ' (' + isbn + ')')
result = json.dumps(book_results)
return HttpResponse(result)
Sample return format of the above function for keyword 'python':
{"titles": ["Python - A Study of Delphic Myth and Its Origins (0520040910)", "Python Machine Learning (1783555149)", "Learn Python the Hard Way - A Very Simple Introduction to the Terrifyingly Beautiful World of Computers and Code (0133124347)", "Natural Language Processing with Python - Analyzing Text with the Natural Language Toolkit (0596555717)", "Python (0201748843)"]}
Now, what I'm not able to figure out is how to loop through the above JSON format to display the results below my input text field. I know I can use the append() JQuery function to add my book titles inside the <li> tags. However, I'm stuck on how to loop through my response result to individually get each book title using a for loop:
for(var book_title of result)
{
console.log(book_title);
}
I'm new to JQuery, and would really appreciate some guidance on this one. Thanks!
Your requirement is quite simple. One way to achieve this is ..please follow the comments
$(function() {
var myDiv = $("#mydiv"); //Assuming there is a div wrapped
var myUl = $('<ul/>'); //blank unordered list object
//Your result from the query
var result = JSON.parse('{"titles": ["Python - A Study of Delphic Myth and Its Origins (0520040910)", "Python Machine Learning (1783555149)", "Learn Python the Hard Way - A Very Simple Introduction to the Terrifyingly Beautiful World of Computers and Code (0133124347)", "Natural Language Processing with Python - Analyzing Text with the Natural Language Toolkit (0596555717)", "Python (0201748843)"]}');
//Iterate through each object
result.titles.forEach(function(item) {
var li = $('<li/>'); //create an li item object
li.append(item); // append the item/txt to the list item
myUl.append(li); //append the list item to the list
});
myDiv.append(myUl) //Append list to the div
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='mydiv'>
<input id='id_book_name' />
</div>
Let us know
Firstly, there is no reason to return a one-key dictionary. Just return the array. So, your result would look more like:
["Python - A Study of Delphic Myth and Its Origins (0520040910)", "Python Machine Learning (1783555149)", "Learn Python the Hard Way - A Very Simple Introduction to the Terrifyingly Beautiful World of Computers and Code (0133124347)", "Natural Language Processing with Python - Analyzing Text with the Natural Language Toolkit (0596555717)", "Python (0201748843)"]
Then, you pass a fourth parameter to $.post, the datatype of JSON, so it always automatically parses it into a JavaScript array.
$.post(url, data, onSuccess, 'json').fail(onFail);
Then you just have a simple array to append to search results.
Make an array of, say, 5 suggestions, and only fill the top 5 (As more would probably be uneccessary). Then use CSS to hide empty ones (Like #auto-complete :empty { display: none; }). Your onSuccess function could look like (Assuming you have a ol or ul element with the id auto-complete that has 5 li elements):
var autoCompleteBoxes = $('#auto-complete li');
$.post(url, data, function(data) {
for (var i = 0; i < 5; i++) {
autoCompleteBoxes[i].text(data[i] || '');
}
}, 'json').fail(function() {
// Reset auto complete boxes if there was a failure.
for (var i = 0; i < 5; i++) {
autoCompleteBoxes[i].text('');
}
$('#auto-complete').hide();
}
I've got a JSON file which has a size of 1Mb.
I tried to implement typeahead.js with a simple example like this:
<div class="container">
<p class="example-description">Prefetches data, stores it in localStorage, and searches it on the client: </p>
<input id="my-input" class="typeahead" type="text" placeholder="input a country name">
</div>
<script type="text/javascript">
// Waiting for the DOM ready...
$(function(){
// applied typeahead to the text input box
$('#my-input').typeahead({
name: 'products',
// data source
prefetch: '../php/products.json',
// max item numbers list in the dropdown
limit: 10
});
});
</script>
But when I launch it Chrome says:
Uncaught QuotaExceededError: Failed to execute 'setItem' on 'Storage':
Setting the value of '__products__itemHash' exceeded the quota.
What can I do? I'm using the typeahead.min.js
You are seeing that error because typeahead prefetch uses localStorage to store the data.
Firstly, storing 1MB of data on the client side is not really good in term of user experience.
Given that, you can still solve the problem with multiple-datasets. This is just a workaround and may not be the most elegant solution but it works perfectly.
The sample data I tested with was >1MB and looks like this
You can view the sample here (It takes a while to open)
Procedure:
First download the entire data using $.getJSON
Then split the data into chunks of 10,000 (just a magical number that worked for me across browsers. Find yours)
Created sets of bloodhounds for each chunk and store everything in an array.
Then initialize typeahead with that array
Code:
$.getJSON('data.json').done(function(data) { // download the entire data
var dataSources = [];
var data = data['friends'];
var i, j, data, chunkSize = 10000; // break the data into chunks of 10,000
for (i = 0, j = data.length; i < j; i += chunkSize) {
tempArray = data.slice(i, i + chunkSize);
var d = $.map(tempArray, function(item) {
return {
item: item
};
});
dataSources.push(getDataSources(d)); // push each bloodhound to dataSources array
}
initTypeahead(dataSources); // initialize typeahead
});
function getDataSources(data) {
var dataset = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('item'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
local: data,
limit: 1 // limited each dataset to 1 because with 76,000 items I have 8 chunks and each chunk gives me 1. So overall suggestion length was 8
});
dataset.initialize();
var src = {
displayKey: 'item',
source: dataset.ttAdapter(),
}
return src;
}
function initTypeahead(data) {
$('.typeahead').typeahead({
highlight: true
}, data); // here is where you use the array of bloodhounds
}
I created a demo here with 20 items and chunkSize of 2 just to show how multiple-datasets would generally work. (Search for Sean or Benjamin)
Hope this helps.