Create table dynamically in jQuery Mobile - jquery-mobile

I am creating table plugin in JQM in html file. It works properly
html page
<table data-role="table" data-mode="columntoggle" class="ui-responsive table" id="service">
....
</table>
But i want to create a table dynamically. when doing this. JQM default plugin isn't apply
My code is
var service_table = $('<table data-role="table" data-mode="columntoggle" class="ui-responsive table" id="service"></table>')
var service_tr_th = $("<tr><th>Name</th></tr>");
var service_tr=$('<tr></tr>');
var service_name_td=$('<td>'+retServiceName+'</td>');
$(service_name_td).appendTo(service_tr);
$(service_tr_th).appendTo("#categories");
$(service_tr).appendTo(service_table);
$(service_table).appendTo("#categories");
in Html page
<div id="categories"></div>

When creating a column toggle table, add THEAD and TBODY, and data-priority="x" to the header cells (see http://demos.jquerymobile.com/1.4.2/table-column-toggle/). Finally call the .table() method to tell jQM to enhance the table:
var service_table = $('<table data-role="table" data-mode="columntoggle" class="ui-responsive table-stroke" id="service"></table>');
var service_tr_th = $("<thead><tr><th data-priority='1'>Name</th><th>Col2</th data-priority='2'></tr></thead>");
var service_tbody = $('<tbody></tbody>');
var service_tr = $('<tr></tr>');
var service_name_td = $('<td>' + retServiceName + '</td><td></td>');
service_name_td.appendTo(service_tr);
service_tr_th.appendTo(service_table);
service_tr.appendTo(service_tbody);
service_tbody.appendTo(service_table);
service_table.appendTo($("#categories"));
service_table.table();
Here is a DEMO
NOTE: you don't need $() around variables that are already jQuery collections e.g. service_tr, service_name_td, etc.

Related

Extract visual text from Google Classic Site page using Apps Script in Google Sheets

I have about 5,000 Classic Google Sites pages that I need to have a Google Apps script under Google Sheets examine one by one, extract the data, and enter that data into the Google Sheet row by row.
I wrote an apps script to use one of the sheets called "Pages" that contains the exactly URL of each page row by row, to run down while doing the extraction.
That in return would get the HTML contents and I would then use regex to extract the data I want which is the values to the right of each of the following...
Job name
Domain owner
Urgency/Impact
ISOC instructions
Which would then write that date under the proper columns in the Google Sheet.
This worked except for one big problem. The HTML is not consistent. Also, ID's and tags were not used so really it makes trying to do this through SitesApp.getPageByUrl not possible.
Here is the code I came up with for that attempt.
function startCollection () {
var masterList = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Pages");
var startRow = 1;
var lastRow = masterList.getLastRow();
for(var i = startRow; i <= lastRow; i++) {
var target = masterList.getRange("A"+i).getValue();
sniff(target)
};
}
function sniff (target) {
var pageURL = target;
var pageContent = SitesApp.getPageByUrl(pageURL).getHtmlContent();
Logger.log("Scraping: ", target);
// Extract the job name
var JobNameRegExp = new RegExp(/(Job name:<\/b><\/td><td style='text-align:left;width:738px'>)(.*?)(\<\/td>)/m);
var JobNameValue = JobNameRegExp.exec(pageContent);
var JobMatch = JobNameValue[2];
if (JobMatch == null){
JobMatch = "NOTE FOUND: " + pageURL;
}
// Extract domain owner
var DomainRegExp = new RegExp(/(Domain owner:<\/b><\/td><td style='text-align:left;width:738px'><span style='font-family:arial,sans,sans-serif;font-size:13px'>)(.*?)(<\/span>)/m);
var DomainValue = DomainRegExp.exec(pageContent);
Logger.log("DUMP1:",SitesApp.getPageByUrl(pageURL).getHtmlContent());
var DomainMatch = DomainValue[2];
if (JobMatch == null){
DomainMatch = "N/A";
}
// Extract Urgency & Impact
var UrgRegExp = new RegExp(/(Urgency\/Impact:<\/b><\/td><td style='text-align:left;width:738px'>)(.*?)(<\/td>)/m);
var UrgValue = UrgRegExp.exec(pageContent);
var UrgMatch = UrgValue[2];
if (JobMatch == null){
UrgMatch = "N/A";
}
// Extract ISOC Instructions
var ISOCRegExp = new RegExp(/(ISOC instructions:<\/b><\/td><td style='text-align:left;width:738px'>)(.*?)(<\/td>)/m);
var ISOCValue = ISOCRegExp.exec(pageContent);
var ISOCMatch = ISOCValue[2];
if (JobMatch == null){
ISOCMatch = "N/A";
}
// Add record to sheet
var row_data = {
Job_Name:JobMatch,
Domain_Owner:DomainMatch,
Urgency_Impact:UrgMatch,
ISOC_Instructions:ISOCMatch,
};
insertRowInTracker(row_data)
}
function insertRowInTracker(rowData) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Jobs");
var rowValues = [];
var columnHeaders = sheet.getDataRange().offset(0, 0, 1).getValues()[0];
Logger.log("Writing to the sheet: ", sheet.getName());
Logger.log("Writing Row Data: ", rowData);
columnHeaders.forEach((header) => {
rowValues.push(rowData[header]);
});
sheet.appendRow(rowValues);
}
So for my next idea, I have thought about using UrlFetchApp.fetch. The one problem I have though is that these pages on that Classics Google Site sit behind a non-shared with the public domain. While using SitesApp.getPageByUrl has the script ask for authorization and works, SitesApp.getPageByUrl does not meaning when it tries to call the direct page, it just gets the Google login page.
I might be able to work around this and turn them public, but I am still working on that.
I am running out of ideas fast on this one and hoping there is another way I have not thought of or seen. What I would really like to do is not even mess with the HTML content. I would like to use apps script under the Google Sheet to just look at the actual data presented on the page and then match a text and capture the value to the right of it.
For example have it go down the list of URLS on sheet called "Pages" and do the following for each page:
Find the following values:
Find the text "Job name:", capture the text to the right of it.
Find the text "Domain owner:", capture the text to the right of it.
Find the text "Urgency/Impact:", capture the text to the right of it.
Find the text "ISOC instructions:", capture the text to the right of it.
Write those values to a new row in sheet called "Jobs" as seen below.
Then move on the the next URL in the sheet called "Pages" and repeat until all rows in the sheet "Pages" have been completed.
Example of the data I want to capture
I have created an exact copy of one of the pages for testing and is public.
https://sites.google.com/site/2020dump/test
An inspect example
The raw HTML of the table which contains all the data I am after.
<tr>
<td style="width:190px"><b>Domain owner:</b></td>
<td style="text-align:left;width:738px">IT.FinanceHRCore </td>
</tr>
<tr>
<td style="width:190px"> <b>Urgency/Impact:</b></td>
<td style="text-align:left;width:738px">Medium (3 - Urgency, 3 - Impact) </td>
</tr>
<tr>
<td style="width:190px"><b>ISOC instructions:</b></td>
<td style="text-align:left;width:738px">None </td>
</tr>
<tr>
<td style="width:190px"></td>
<td style="text-align:left;width:738px"> </td>
</tr>
</tbody>
</table>
Any examples of how I can accomplish this? I am not sure how from an apps script perspective to go about not looking at HTML and only looking at the actual data displayed on the page. For example looking for the text "Job name:" and then grabbing the text to the right of it.
The goal at the end of the day is to transfer the data from each page into one big Google Sheet so we can kill off the Google Classic Site.
I have been scraping data with apps script using regular expressions for a while, but I will say that the formatting of this page does make it difficult.
A lot of the pages that I scrape have tables in them so I made a helper script that will go through and clean them up and turn them into arrays. Copy and paste the script below into a new google script:
function scrapetables(html,startingtable,extractlinksTF) {
var totaltables = /<table.*?>/g
var total = html.match(totaltables)
var tableregex = /<table[\s\S]*?<\/table>/g;
var tables = html.match(tableregex);
var arrays = []
var i = startingtable || 0;
while (tables[i]) {
var thistable = []
var rows = tables[i].match(/<tr[\s\S]*?<\/tr>/g);
if(rows) {
var j = 0;
while (rows[j]) {
var thisrow = tablerow(rows[j])
if(thisrow.length > 2) {
thistable.push(tablerow(rows[j]))
} else {thistable.push(thisrow)}
j++
}
arrays.push(thistable);
}
i++
}
return arrays;
}
function removespaces(string) {
var newstring = string.trim().replace(/[\r\n\t]/g,'').replace(/ /g,' ');
return newstring
}
function tablerow(row,extractlinksTF) {
var cells = row.match(/<t[dh][\s\S]*?<\/t[dh]>/g);
var i = 0;
var thisrow = [];
while (cells[i]) {
thisrow.push(removehtmlmarkup(cells[i],extractlinksTF))
i++
}
return thisrow
}
function removehtmlmarkup(string,extractlinksTF) {
var string2 = removespaces(string.replace(/<\/?[A-Za-z].*?>/g,''))
var obj = {string: string2}
//check for link
if(/<a href=.*?<\/a>/.test(string)) {
obj['link'] = /<a href="(.*?)"/.exec(string)[1]
}
if(extractlinksTF) {
return obj;
} else {return string2}
}
Running this got close, but at the moment, this doesn't handle nested tables well so I cleaned up the input by sending only the table that we want by isolating it with a regular expression:
var tablehtml = /(<table[\s\S]{200,1000}Job Name[\s\S]*?<\/table>)/im.exec(html)[1]
Your parent function will then look like this:
function sniff(pageURL) {
var html= SitesApp.getPageByUrl(pageURL).getHtmlContent();
var tablehtml = /(<table[\s\S]{200,1000}Job Name[\s\S]*?<\/table>)/im.exec(html)[1]
var table = scrapetables(tablehtml);
var row_data =
{
Job_Name: na(table[0][3][1]), //indicates the 1st table in the html, row 4, cell 2
Domain_Owner: na(table[0][4][1]), // indicates 1st table in the html, row 5, cell 2 etc...
Urgency_Impact: na(table[0][5][1]),
ISOC_Instructions: na(table[0][6][1])
}
insertRowInTracker(row_data)
}
function na(string) {
if(string) {
return string
} else { return 'N/A'}
}

Selenium + MVC4: how to find element contained in a specific parent element

Im doing MVC4 application and Im creating Selenium tests for it. My problem is that I want to find element <ul class="connectors ui-sortable"></ul> but only contained in <li class="empty zoneLi ui-droppable" data-order="X">
My solution was one added below, but it's not working:
private static IWebElement FindZoneByDataOrder(IWebDriver webDriver, int dataOrderId)
{
var parentForDropElement = webDriver.FindElement(By.XPath("//li[#data-order='" + dataOrderId + "']"));
var dropElement = parentForDropElement.FindElement(By.XPath("//ul[#class='connectors ui-sortable']"));
return dropElement;
}
part of my HTML page:
....
<div class="zones-system-creator" style="min-height: 430px;">
<ul id="zonesCreateSystem" class="">
<li data-zoneid="24829" class="empty zoneLi ui-droppable" data-order="1">
<div id="warningInfoBoxContainer"></div>
<ul class="products ui-sortable"></ul>
<ul class="connectors ui-sortable"></ul>
</li>
</ul>
</div>
....
UPDATE1: I changed zoneID to dataOrderId, because we care only about dataOrderId.
I see few issues in your code. You talked about data-order, but passed zoneId in the method. So I have added another parameter called dataOrder and searching for the LI element which has both zone and data order attributes:
"//li[#data-zoneid='" + zoneId + "'][#data-order='"+dataOrder+"'"]
Then searching for ul element of having specified class. Also combined finding the element into a single line:
Code:
private static IWebElement FindZoneByDataOrder(IWebDriver webDriver, int zoneId, int dataOrder)
{
return driver.findElement(By.xpath("//li[#data-zoneid='" + zoneId + "'][#data-order='"+dataOrder+"']//ul[#class='connectors ui-sortable']"))");
}
Edited Answer following question edit:
return driver.findElement(By.xpath("//li[#data-order='"+dataOrderId+"']//ul[starts-with(#class, 'connectors')]"));
I found 2 working solutions if you want to find one element inside the other with Selenium:
result = webDriver.FindElement(By.XPath("//li[#data-order='" + dataOrderId+ "']"));
var result2 = result.FindElement(By.XPath(".//ul[#class='connectors']"));
result = webDriver.FindElement(By.CssSelector("li[data-order='" + dataOrderId+ "']"));
result = result.FindElement(By.CssSelector("ul[class='connectors']"));

Implementing preview for markdown text

I am working on Ruby on Rails project and I have implemented markdown syntax for some text descriptions in my project using redcarpet gem.
It works like charm allowing to convert markdown text to HTML as simply as
<%= markdown some_text_variable %>
But now I want to implement preview feature rendering just small part of the full text.
The following naive construction
<%= markdown some_text_variable[0..preview_length] %>
will not work because it can easily break down MD syntax resulting in confusing constructions (imagine, for example, spliting original string on the half of image link).
I came up with
<%= markdown some_text_variable[0..preview_length].split(/\r?\n/)[0..-2].join("\r\n")) %>
but it does not deal, for example, with code blocks.
Is there any way to implement such kind of preview for MD text?
Using markdown.js and / or showdown should work. Here's a StackO with the same question and answer. I personally have used showdown in an Ember app before to render a live preview of the text as it's being typed (via 2-way data binding), and it worked flawlessly.
In the fiddle below, I wrote a little Showdown parser that takes in a string of markdown, splits it on a newline (returns an array of tags), and iterates through the array. On each iteration, it removes the tags, checks the length of the resulting string, and then compares it to the max character count for the preview. Once the next iteration surpasses the max character count, it returns the preview. The do loop ensures that you will always get at least one blob of html as a preview.
Fiddle
$(function() {
var converter = new Showdown.converter();
var previewMax = 200;
$('button').click(function() {
var content = $('#markdown').val(),
charCount = 0,
i = 0,
output = '';
if (!content) {
return $('div.preview').html("Please enter some text.");
}
var mark = converter.makeHtml(content);
var mark_arr = mark.split('\n');
while (charCount < previewMax) {
var html = mark_arr[i];
var text = htmlStrip(html);
if ((charCount + text.length) > previewMax) {
var overflow = (charCount + text.length) - previewMax;
var clipAmount = text.length - overflow;
html = jQuery.truncate(mark_arr[i], { length: clipAmount });
}
output += html;
charCount += text.length;
i++;
};
$('div.preview').html(output);
$('div.full').html(mark);
});
function htmlStrip (html) {
var div = document.createElement('div');
div.innerHTML = html;
var text = div.textContent || div.innerText || "";
return text;
}
});
REVISION
I updated the function using jQuery Truncate to cut the final string into an elipses so that all your previews are the same length as the others. Also, I realized that the original function returned a long string of undefined' over and over when no text was entered, so there is a check to eliminate that. Since this loop will always return at least one html item now, I changed the do loop to a while loop for easier reading. Finally, if you want your truncation to always end at a word boundary, pass the words: true option when you call it. Obviously, this will not give you the same level of truncation for every preview, but it will improve legibility. That's it!
I want to share my preview version it was quite simple with showdown.js and prism.js syntax highlighting.
Prism.js is syntaxing easily with JavaScript and CSS. All you need to pick specific languages and download it to assets folder. Or you can specify it to specific pages.
This is going to happen in realtime preview, in a form.
In Rails form:
<div class="col-md-12">
<div class="form-group">
<%= f.label :body %>
<%= f.text_area :body, class: "form-control", rows: 10 %>
</div>
</div>
<div class="col-md-12">
<h1> Preview Markdown </h1>
<div class="form-group markdownOutput"></div>
</div>
And add this script right below a form page.
<script>
function mkdown(){
var converter = new showdown.Converter(),
$post_body = $("#post_body");
// This line will keep adding new rows for textarea.
function postBodyLengthDetector(post_body){
var lines = post_body.val().split("\n");
post_body.prop('rows', lines.length+5);
}
// Textarea rows in default '10', when focusing on this. It will expand.
$post_body.focus(function(){
postBodyLengthDetector($(this));
$('.markdownOutput').html(converter.makeHtml($post_body.val()));
});
// All simple magic goes here, each time when texting anything into textarea
//it will be generated to markdown. You are able to see preview right below of textarea.
$post_body.keyup(function() {
postBodyLengthDetector($(this));
var value = $( this ).val(),
html = converter.makeHtml(value);
$('.markdownOutput').html(html);
});
}
$(mkdown);
$(document).on("turbolinks:load", mkdown);
</script>

add collapsible into another collapsibles (nested collapsibles) in JQM

I want to get this:
But I need to create the HTML dynamically because it's loaded via AJAX from an external ressource.
I already asked this question here (JQM: Dynamic nested collapsibles - $(collapsible).collapsible() & $(collapsible).trigger('create')) but I got it completly wrong with lists and collapsibles, so I figured a new question would be better.
Here is what I got so far:
function loadTipps() {
console.log("Lade Tipps..");
var categoriesURL = tippsURL+"?type=kategorien&callback=?"; // url for the categories
$.getJSON(categoriesURL,function(data) {
console.log("Lade Kategorien..");
var DIV_tipps_komplett = $("#tipps_komplett");
$.each(data, function(key,value){
var kategorie_ID = value.id;
var kategorie_NAME = value.name;
var collapsible_HTML = $('<div data-role="collapsible"></div>');
var kategorie_Ueberschrift_HTML = $('<h3>'+kategorie_NAME+'</h3>');
var tipps_kategorie_HTML = $('<div id="tipps_kategorie'+kategorie_ID+'" data-role="collapsible-set"></div>');
var tippURL = tippsURL+"?type=tipp&kat_id="+value.id+"&callback=?"; // url for the tipps of the current category
$.getJSON(tippURL,function(data2) {
$.each(data2, function(key2,value2){
var tipp_Ueberschrift_Text_HTML = '<div data-role="collapsible"><b>'+value2.name+'</b><p>'+value2.text+'</p><br></div>';
tipps_kategorie_HTML.append(tipp_Ueberschrift_Text_HTML);
}); //<--each
});//<--getJSON
collapsible_HTML.append(kategorie_Ueberschrift_HTML);
collapsible_HTML.append(tipps_kategorie_HTML);
DIV_tipps_komplett.append(collapsible_HTML);
});//<--each
DIV_tipps_komplett.trigger('create');
});//<--getJSON
}
This results in:
As you can see, the items in the first collapsible set are not in another collapsible set. Any ideas why?
Try changing the line:
var tipp_Ueberschrift_Text_HTML = '<div data-role="collapsible"><b>'+value2.name+'</b><p>'+value2.text+'</p><br></div>';
to
var tipp_Ueberschrift_Text_HTML = '<div data-role="collapsible"><h3>'+value2.name+'</h3><p>'+value2.text+'</p><br></div>';
The Collapsible needs the <h3> element instead of <b> to render.
Here is a demo of your exact code with the AJAX calls removed: http://jsfiddle.net/ezanker/RkLuV/
By the way, this is pretty much the answer I gave you here: collapsible list with collapsed items in jqm, it would probably have been better to continue the conversation there.

Angular pagination not updating when bound list changes due to filtering on an input text box

Here's the scenario:
I am using an ASP.NET MVC site with Angular JS and Boostrap UI. I have a dynamic ul list populated by data fed through a controller call to AngularJS, filtering on that list through an input search box. The list is also controlled through pagination (UI Bootstrap control) that I've setup to show 10 results per page for the list of 100 or so items. This list is filtered as the user types in the search box, however I would like the pagination to update as well so consider the following example:
The list has 10 pages of items (100 items), the user types some text in the input search box which filters the list down to 20 or so items, so the pagination should be updated from 10 pages to two pages.
I figure there must be a $watch setup somewhere, perhaps on the list items after it has been filtered and then update the pagination page count, however I'm pretty new to AngularJS so can someone please explain to me how this could be done?
Thanks very much. I have posted my code below:
<div data-ng-app="dealsPage">
<input type="text" data-ng-model="cityName" />
<div data-ng-controller="DestinationController">
<ul>
<li data-ng-repeat="deals in destinations | filter: cityName |
startFrom:currentPage*pageSize | limitTo:pageSize">{{deals.Location}}</li>
</ul>
<br />
<pagination rotate="true" num-pages="noOfPages" current-page="currentPage"
max-size="maxSize" class="pagination-small" boundary-links="true"></pagination>
</div>
var destApp = angular.module('dealsPage', ['ui.bootstrap', 'uiSlider']);
destApp.controller('DestinationController', function ($scope, $http) {
$scope.destinations = {};
$scope.currentPage = 1;
$scope.pageSize = 10;
$http.get('/Deals/GetDeals').success(function (data) {
$scope.destinations = data;
$scope.noOfPages = data.length / 10;
$scope.maxSize = 5;
});
});
destApp.filter('startFrom', function () {
return function (input, start) {
start = +start; //parse to int
return input.slice(start);
};
});
Because your pagination is a combination of chained filters, Angular has no idea that when cityName changes, it should reset currentPage to 1. You'll need to handle that yourself with your own $watch.
You'll also want to adjust your startFrom filter to say (currentPage - 1) * pageSize, otherwise, you always start at page 2.
Once you get that going, you'll notice that your pagination is not accurate, because it's still based on destination.length, and not the filtered sub-set of destinations. For that, you're going to need to move your filtering logic from your view to your controller like so:
http://jsfiddle.net/jNYfd/
HTML
<div data-ng-app="dealsPage">
<input type="text" data-ng-model="cityName" />
<div data-ng-controller="DestinationController">
<ul>
<li data-ng-repeat="deals in filteredDestinations | startFrom:(currentPage - 1)*pageSize | limitTo:pageSize">{{deals.Location}}</li>
</ul>
<br />
<pagination rotate="true" num-pages="noOfPages" current-page="currentPage" max-size="maxSize" class="pagination-small" boundary-links="true"></pagination>
</div>
JavaScript
var destApp = angular.module('dealsPage', ['ui.bootstrap']);
destApp.controller('DestinationController', function ($scope, $http, $filter) {
$scope.destinations = [];
$scope.filteredDestinations = [];
for (var i = 0; i < 1000; i += 1) {
$scope.destinations.push({
Location: 'city ' + (i + 1)
});
}
$scope.pageSize = 10;
$scope.maxSize = 5;
$scope.$watch('cityName', function (newCityName) {
$scope.currentPage = 1;
$scope.filteredDestinations = $filter('filter')($scope.destinations, $scope.cityName);
$scope.noOfPages = $scope.filteredDestinations.length / 10;
});
});
destApp.filter('startFrom', function () {
return function (input, start) {
start = +start; //parse to int
return input.slice(start);
};
});
The version shared on jsfiddle is compatible with ui-bootstrap 0.5.0 but from 0.6.0 onwards there have been breaking changes.
Here is a version that uses the following libraries:
angular 1.2.11
angular-ui-bootstrap 0.10.0
bootstrap 3.1.0
Here is a plunker for the same:
Angular UI Bootstrap Pagination
Hello I tried to hook this up with Firebase using Angular Fire and it only works after I type something in the search input. In the $scope.$watch method, I used Angular Fire's orderByPriorityFilter to convert the object to an array.
$scope.$watch('search', function(oldTerm, newTerm) {
$scope.page = 1;
// Use orderByPriorityFilter to convert Firebase Object into Array
$scope.filtered = filterFilter(orderByPriorityFilter($scope.contacts), $scope.search);
$scope.lastSearch.search = oldTerm;
$scope.contactsCount = $scope.filtered.length;
});
Initial load doesn't load any contacts. It's only after I start typing in the input search field.

Resources