adding multiple (dynamic number) google charts in page - charts

Am trying to create a test report from the xml file generated from the test suite run. I have to generate the google line chart for each activity in an application node in xml. Its dynamic and we dont know how many activities will be there under application tag.
so far i tried to generate the line charts using the callback method in a for loop but, all the graphs are having the same data. when i debugged the code i found that the code in call back method to create the datatable is always executing for the last activity and generating the same chart for each activity.
here is the code i tried
html
<div id="container">
<div id="report" class="table-responsive">
<select id="app" name="app" aria-placeholder="Select Application">
<option>-- Select Application --</option>
</select>
<select id="activity" name="activity" aria-placeholder="Select Activity">
<option>-- Select Activity --</option>
</select>
<select id="type" name="type" aria-placeholder="Select StartupType">
<option value="coldstart" >Cold</option>
<option value="warmstart" selected>Warm</option>
</select>
<br/>
<div id="chartContainer">
</div>
</div>
<div align="center" class="loader">
<img src="images/loader.gif" id="load" width="400" height="400" align="absmiddle" />
</div>
</div>
javascript
var appXml;
var summaryXml;
$(document).ready(function () {
prepareCharts();
$("#app").change(function () {
var app = $(this).val();
if (app != "") {
$(appXml).find('package').each(function () {
if ($(this).attr('appname') == app) {
var options = '<option value="">-- Select activity --</option>';
$(this).find('activity').each(function () {
options += '<option value="' + $(this).attr('activityname') + '">' + $(this).attr('activityname') + '</option>';
});
$('#activity').html(options);
}
});
}
});
$("#activity").change(function () {
if ($(this).val() != "")
drawActivityChart();
else
drawActivityCharts(appXml, $('#type').val());
});
$('#type').change(function () {
var type = $(this).val();
if ($('#activity').val() == "")
drawActivityCharts(appXml, type);
else
drawActivityChart();
});
});
function prepareCharts() {
$.ajax({
type: "GET",
url: "Startuptime.xml",
dataType: "xml",
success: drawCharts
});
}
function drawCharts(xml) {
console.log('drawing charts');
appXml = xml;
prepareDropdowns(xml);
drawActivityCharts(xml);
}
function prepareDropdowns(xml) {
var options = '<option value="">-- Select application --</option>';
$(xml).find('package').each(function () {
options += '<option value="' + $(this).attr('appname') + '">' + $(this).attr('appname') + '</option>';
});
$('#app').html(options);
$('#app option:nth-child(2)').attr('selected', 'selected').change();
}
function drawActivityCharts(xml, type) {
$('#chartContainer').children().remove();
if (typeof type === 'undefined')
type = 'warmstart';
google.charts.load('current', { 'packages': ['corechart'] });
var app = $('#app').val();
$(xml).find('package').each(function () {
var that = this;
if ($(that).attr('appname') == app) {
var i = 1;
$(that).find('activity').each(function () {
var activityName = $(this).attr('activityname');
console.log(i);
console.log(activityName);
i++;
if ($(this).find(type).length > 0) {
that = this;
$('#chartContainer').append('<div id="' + activityName + '"></div>')
google.charts.setOnLoadCallback(function () {
var data = new google.visualization.DataTable();
data.addColumn('number', 'Occurance');
data.addColumn('number', 'Time');
var row = 1;
$(that).children(type).find('displaytime').each(function () {
data.addRow([row, parseFloat($.trim($(this).find('timetoinitialdisplay').text()))]);
console.log(row + " " + parseFloat($.trim($(this).find('timetoinitialdisplay').text())));
row++;
});
// Set chart options
var options = {
'title': activityName,
'width': 800,
'height': 200
};
// Instantiate and draw our chart, passing in some options.
var chart = new google.visualization.LineChart(document.getElementById(activityName));
chart.draw(data, options);
});
}
});
}
});
}
function drawActivityChart() {
$('#chartContainer').children().remove();
google.charts.load('current', { 'packages': ['corechart'] });
var app = $('#app').val();
var activity = $('#activity').val();
var type = $('#type').val();
$(appXml).find('package').each(function () {
var that = this;
if ($(that).attr('appname') == app) {
$(that).find('activity').each(function () {
var activityName = $(this).attr('activityname');
if (activityName == activity) {
if ($(this).find(type).length > 0) {
that = this;
$('#chartContainer').append('<div id="' + activityName + '"></div>')
google.charts.setOnLoadCallback(function () {
var data = new google.visualization.DataTable();
data.addColumn('number', 'Occurance');
data.addColumn('number', 'Time');
var row = 1;
$(that).find('displaytime').each(function () {
data.addRow([row, parseFloat($.trim($(this).find('timetoinitialdisplay').text()))]);
console.log(row + " " + parseFloat($.trim($(this).find('timetoinitialdisplay').text())));
row++;
});
// Set chart options
var options = {
'title': activityName,
'width': 800,
'height': 200
};
// Instantiate and draw our chart, passing in some options.
var chart = new google.visualization.LineChart(document.getElementById(activityName));
chart.draw(data, options);
});
}
}
});
}
});
}
drawActivityCharts() is the method which has to draw the activity charts
and xml schema will be like below.
<?xml version='1.0' encoding='UTF-8' ?>
<appstartuptime>
<package appname="appname" name="packagename" packageversion="version">
<activity activityname="activityname">
<coldstart numberoftimes="1">
<displaytime>
<timetoinitialdisplay>841</timetoinitialdisplay>
</displaytime>
</coldstart>
</activity>
<activity activityname="activityname">
<warmstart numberoftimes="2">
<displaytime>
<timetoinitialdisplay>454</timetoinitialdisplay>
</displaytime>
<displaytime>
<timetoinitialdisplay>467</timetoinitialdisplay>
</displaytime>
</warmstart>
</activity>
</package>
</appstartuptime>

both google.charts.load and google.charts.setOnLoadCallback only need to be called once per page load.
in addition, google.charts.load will wait on the page to load by default,
as such, it can be used in place of $(document).ready
you can also include google's callback in the load statement.
google.charts.load('current', {
callback: drawChart,
packages:['corechart']
});
or use the promise it returns.
google.charts.load('current', {
packages:['corechart']
}).then(drawChart);
given this, recommend loading google first,
then you can draw as many charts as needed.
i didn't go thru all of the code, but something similar to the following should work...
var appXml;
var summaryXml;
google.charts.load('current', {
packages:['corechart']
}).then(function () {
prepareCharts();
$("#app").change(function () {
var app = $(this).val();
if (app != "") {
$(appXml).find('package').each(function () {
if ($(this).attr('appname') == app) {
var options = '<option value="">-- Select activity --</option>';
$(this).find('activity').each(function () {
options += '<option value="' + $(this).attr('activityname') + '">' + $(this).attr('activityname') + '</option>';
});
$('#activity').html(options);
}
});
}
});
$("#activity").change(function () {
if ($(this).val() != "")
drawActivityChart();
else
drawActivityCharts(appXml, $('#type').val());
});
$('#type').change(function () {
var type = $(this).val();
if ($('#activity').val() == "")
drawActivityCharts(appXml, type);
else
drawActivityChart();
});
});
function prepareCharts() {
$.ajax({
type: "GET",
url: "Startuptime.xml",
dataType: "xml",
success: drawCharts
});
}
function drawCharts(xml) {
console.log('drawing charts');
appXml = xml;
prepareDropdowns(xml);
drawActivityCharts(xml);
}
function prepareDropdowns(xml) {
var options = '<option value="">-- Select application --</option>';
$(xml).find('package').each(function () {
options += '<option value="' + $(this).attr('appname') + '">' + $(this).attr('appname') + '</option>';
});
$('#app').html(options);
$('#app option:nth-child(2)').attr('selected', 'selected').change();
}
function drawActivityCharts(xml, type) {
$('#chartContainer').children().remove();
if (typeof type === 'undefined')
type = 'warmstart';
var app = $('#app').val();
$(xml).find('package').each(function () {
var that = this;
if ($(that).attr('appname') == app) {
var i = 1;
$(that).find('activity').each(function () {
var activityName = $(this).attr('activityname');
console.log(i);
console.log(activityName);
i++;
if ($(this).find(type).length > 0) {
that = this;
$('#chartContainer').append('<div id="' + activityName + '"></div>')
var data = new google.visualization.DataTable();
data.addColumn('number', 'Occurance');
data.addColumn('number', 'Time');
var row = 1;
$(that).children(type).find('displaytime').each(function () {
data.addRow([row, parseFloat($.trim($(this).find('timetoinitialdisplay').text()))]);
console.log(row + " " + parseFloat($.trim($(this).find('timetoinitialdisplay').text())));
row++;
});
// Set chart options
var options = {
'title': activityName,
'width': 800,
'height': 200
};
// Instantiate and draw our chart, passing in some options.
var chart = new google.visualization.LineChart(document.getElementById(activityName));
chart.draw(data, options);
}
});
}
});
}
function drawActivityChart() {
$('#chartContainer').children().remove();
var app = $('#app').val();
var activity = $('#activity').val();
var type = $('#type').val();
$(appXml).find('package').each(function () {
var that = this;
if ($(that).attr('appname') == app) {
$(that).find('activity').each(function () {
var activityName = $(this).attr('activityname');
if (activityName == activity) {
if ($(this).find(type).length > 0) {
that = this;
$('#chartContainer').append('<div id="' + activityName + '"></div>')
var data = new google.visualization.DataTable();
data.addColumn('number', 'Occurance');
data.addColumn('number', 'Time');
var row = 1;
$(that).find('displaytime').each(function () {
data.addRow([row, parseFloat($.trim($(this).find('timetoinitialdisplay').text()))]);
console.log(row + " " + parseFloat($.trim($(this).find('timetoinitialdisplay').text())));
row++;
});
// Set chart options
var options = {
'title': activityName,
'width': 800,
'height': 200
};
// Instantiate and draw our chart, passing in some options.
var chart = new google.visualization.LineChart(document.getElementById(activityName));
chart.draw(data, options);
}
}
});
}
});
}

Related

L.DomUtil.get() modifiers don't update HTML data

Creating a map with markers displayed on it. When clicking a marker, this one has to display a Popup. I extended the L.Popup like this
L.InfrastructurePopup = L.Popup.extend({
options: {
template : "<form id='popup-form'>\
<div>\
<label for='problem'>Problem</label>\
<textarea id='problem' rows='4' cols='46' placeholder='Type your text here'></textarea>\
</div>\
<div>\
<label for='solution'>Solution</label>\
<textarea id='solution' rows='4' cols='46' placeholder='Type your text here'></textarea>\
</div>\
<button id='button-submit' class='btn btn-primary' type='button'>Submit</button>\
</form>",
},
setContent: function () {
this._content = this.options.template;
this.update();
return this;
},
initializeForm(layer, callback)
{
var problem = L.DomUtil.get('problem');
problem.textContent = layer.options.problem ? layer.options.problem : "";
problem.addEventListener('change', (e) =>
{
layer.options.problem = problem.value;
});
var solution = L.DomUtil.get('solution');
solution.textContent = layer.options.solution ? layer.options.solution : "";
solution.addEventListener('change', (e) =>
{
layer.options.solution = solution.value;
});
var buttonSubmit = L.DomUtil.get('button-submit');
buttonSubmit.addEventListener('click', (e) =>
{
callback(layer);
});
}
});
L.infrastructurePopup = function (options, source)
{
return new L.InfrastructurePopup(options, source);
};
I linked it into a custom Marker called InfrastructureMarker that has one and only popup , a InfrastructurePopup. So when it calls the openPopup() function it loads the popup on the map [ map.addLayer(popup) ] and give me the correct datas thanks to method initializeForm() that I call after the addLayer(popup) method.
L.Map.include({
openInfrastructurePopup: function (layer, callback)
{
this.closePopup();
layer._popup._isOpen = true;
this.addLayer(layer._popup);
layer._popup.initializeForm(layer, callback);
}
});
L.InfrastructureMarker = L.Marker.extend({
openPopup: function (callback)
{
if (this._popup && this._map && !this._map.hasLayer(this._popup))
{
this._popup.setLatLng(this._latlng);
this._map.openInfrastructurePopup(this, callback);
}
return this;
},
togglePopup: function (callback)
{
if (this._popup)
{
if (this._popup._isOpen)
{
this._popup._isOpen = false;
this.closePopup();
}
else
{
this.openPopup(callback);
}
}
return this;
},
bindPopup: function (callback, options)
{
var anchor = L.point(this.options.icon.options.popupAnchor || [0, 0]);
anchor = anchor.add(L.Popup.prototype.options.offset);
if (options && options.offset)
{
anchor = anchor.add(options.offset);
}
options = L.extend({offset: anchor}, options);
if (!this._popupHandlersAdded)
{
this
.on('click', () => {this.togglePopup(callback)}, this)
.on('remove', this.closePopup, this)
.on('move', this._movePopup, this);
this._popupHandlersAdded = true;
}
this._popup = new L.infrastructurePopup(options, this).setContent();
return this;
},
});
L.infrastructureMarker = function (latlng, options)
{
return new L.InfrastructureMarker(latlng, options);
};
But if I decide to click on one marker, then on another one without closing the first one, it loads the template, but initializeForm(callback) doesn't change the datas. I checked all the datas to know if it was empty or something but everything worked, I absolutely don't know where the problem is. I suppose the popup is not yet set on the DOM before my L.DomUtils.get fire but I shouldn't see undefined elements in console.log when I'm getting them.
I actually found what was happening :
Actually, when the L.map calls its closePopup function , it destroys the layer.
So after that, it creates a new one to display. BUT the remaining HTML from the previous kind of still exists.
So I finally bound exact same Ids to two HTML tags. Heresy !
My solution became what's next :
L.InfrastructurePopup = L.Popup.extend({
setContent: function (layer)
{
var template = "<form id='popup-form'>\
<div>\
<label for='problem'>Problème Identifié</label>\
<textarea id='" + layer._leaflet_id + "-problem' rows='4' cols='46' placeholder='Type your text here'></textarea>\
</div>\
<div>\
<label for='solution'>Solution Proposée</label>\
<textarea id='" + layer._leaflet_id + "-solution' rows='4' cols='46' placeholder='Type your text here'></textarea>\
</div>\
<button id='" + layer._leaflet_id + "-button-submit' class='btn btn-primary' type='button'>Submit</button>\
</form>";
this._content = template;
this.update();
return this;
},
initializeForm: function(layer, callback)
{
console.log(L.DomUtil.get(layer._leaflet_id+'-problem'));
var problem = L.DomUtil.get(layer._leaflet_id + '-problem');
problem.textContent = layer.options.problem ? layer.options.problem : "";
problem.addEventListener('change', (e) =>
{
layer.options.problem = problem.value;
});
var solution = L.DomUtil.get(layer._leaflet_id + '-solution');
solution.textContent = layer.options.solution ? layer.options.solution : "";
solution.addEventListener('change', (e) =>
{
layer.options.solution = solution.value;
});
var buttonSubmit = L.DomUtil.get(layer._leaflet_id + '-button-submit');
buttonSubmit.addEventListener('click', (e) =>
{
callback(layer);
});
}
});
L.infrastructurePopup = function (options, source)
{
return new L.InfrastructurePopup(options, source);
};
Calling setContent when creating my InfrastructurePopup with the layer_id and set it into my template made it work.
I got : '97-problem' or '99-problem' and '97-solution' or '99-solution

Tinymce multiple instances in same page

I am using tinymce multiple instances in same page. When I try to insert image, I can but it will be added always in last text area fields( I have same 10 textarea with different name and id).
Html
<div class="admin__field field field-options_options_fieldset3ad06b8f111bff0225b3ee56e2a93cc4_content_1 with-note">
<label class="label admin__field-label" for="options_fieldset3ad06b8f111bff0225b3ee56e2a93cc4_content_1" data-ui-id="adminhtml-widget-form-field-wysiwygeditor-0-text-parameters-content-1-label"><span>Content 1</span></label>
<div class="admin__field-control control">
<textarea id="options_fieldset3ad06b8f111bff0225b3ee56e2a93cc4_content_13662927431522736609" name="parameters[content_1]" class="textarea admin__control-textarea wysiwyg-editor" rows="5" cols="15" data-ui-id="product-tabs-attributes-tab-fieldset-element-textarea-parameters[content_1]" aria-hidden="true"></textarea>
<button id="id_5abf1b8e544f38a8ca21aa8815613abd" title="WYSIWYG Editor" type="button" class="action-default scalable action-wysiwyg hidden" onclick="basewidgetWysiwygEditor.open('http://cei-shop.local/admin/catalog/product/wysiwyg/key/0367c4936326c7be6bf0fda5c407d274c1f3e4a9922173f53b72e75411d13406/', 'options_fieldset3ad06b8f111bff0225b3ee56e2a93cc4_content_13662927431522736609')" data-ui-id="widget-button-0">
<span>WYSIWYG Editor</span>
</button>
<button type="button" class="scalable action-show-hide" style="" id="toggleoptions_fieldset3ad06b8f111bff0225b3ee56e2a93cc4_content_13662927431522736609"><span><span><span>Show / Hide Editor</span></span></span></button>
</div>
</div>
<div class="admin__field field field-options_options_fieldset3ad06b8f111bff0225b3ee56e2a93cc4_content_2 with-note">
<label class="label admin__field-label" for="options_fieldset3ad06b8f111bff0225b3ee56e2a93cc4_content_2" data-ui-id="adminhtml-widget-form-field-wysiwygeditor-1-text-parameters-content-2-label"><span>Content 2</span></label>
<div class="admin__field-control control">
<textarea id="options_fieldset3ad06b8f111bff0225b3ee56e2a93cc4_content_26925735391522736609" name="parameters[content_2]" class="textarea admin__control-textarea wysiwyg-editor" rows="5" cols="15" data-ui-id="product-tabs-attributes-tab-fieldset-element-textarea-parameters[content_2]" aria-hidden="true"></textarea>
<button id="id_504c308d5e46c342e202e961e97b2904" title="WYSIWYG Editor" type="button" class="action-default scalable action-wysiwyg hidden" onclick="basewidgetWysiwygEditor.open('http://cei-shop.local/admin/catalog/product/wysiwyg/key/0367c4936326c7be6bf0fda5c407d274c1f3e4a9922173f53b72e75411d13406/', 'options_fieldset3ad06b8f111bff0225b3ee56e2a93cc4_content_26925735391522736609')" data-ui-id="widget-button-1">
<span>WYSIWYG Editor</span>
</button>
<button type="button" class="scalable action-show-hide" style="" id="toggleoptions_fieldset3ad06b8f111bff0225b3ee56e2a93cc4_content_26925735391522736609"><span><span><span>Show / Hide Editor</span></span></span></button>
</div>
</div>
setup.js
define([
'jquery',
'underscore',
'tinymce',
'mage/adminhtml/wysiwyg/tiny_mce/html5-schema',
'mage/translate',
'prototype',
'mage/adminhtml/events',
'mage/adminhtml/browser'
], function(jQuery, _, tinyMCE, html5Schema) {
vesBaseTinyMceWysiwygSetup = Class.create();
vesBaseTinyMceWysiwygSetup.prototype = {
mediaBrowserOpener: null,
mediaBrowserTargetElementId: null,
initialize: function(htmlId, config) {
if (config.baseStaticUrl && config.baseStaticDefaultUrl) {
tinyMCE.baseURL = tinyMCE.baseURL.replace(config.baseStaticUrl, config.baseStaticDefaultUrl);
}
this.id = htmlId;
this.config = config;
this.schema = config.schema || html5Schema;
_.bindAll(this, 'beforeSetContent', 'saveContent', 'onChangeContent', 'openFileBrowser', 'updateTextArea');
varienGlobalEvents.attachEventHandler('tinymceChange', this.onChangeContent);
varienGlobalEvents.attachEventHandler('tinymceBeforeSetContent', this.beforeSetContent);
varienGlobalEvents.attachEventHandler('tinymceSaveContent', this.saveContent);
if (typeof tinyMceEditors == 'undefined') {
tinyMceEditors = $H({});
}
tinyMceEditors.set(this.id, this);
},
setup: function(mode) {
if (this.config.widget_plugin_src) {
tinyMCE.PluginManager.load('magentowidget', this.config.widget_plugin_src);
}
if (this.config.plugins) {
this.config.plugins.each(function(plugin) {
tinyMCE.PluginManager.load(plugin.name, plugin.src);
});
}
if (jQuery.isReady) {
tinyMCE.dom.Event.domLoaded = true;
}
tinyMCE.init(this.getSettings(mode));
},
getSettings: function(mode) {
var plugins = 'inlinepopups,safari,pagebreak,style,layer,table,advhr,advimage,emotions,iespell,media,searchreplace,contextmenu,paste,directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras',
self = this;
if (this.config.widget_plugin_src) {
plugins = 'magentowidget,' + plugins;
}
var magentoPluginsOptions = $H({});
var magentoPlugins = '';
if (this.config.plugins) {
this.config.plugins.each(function(plugin) {
magentoPlugins = plugin.name + ',' + magentoPlugins;
magentoPluginsOptions.set(plugin.name, plugin.options);
});
if (magentoPlugins) {
plugins = '-' + magentoPlugins + plugins;
}
}
var settings = {
mode: (mode != undefined ? mode : 'none'),
elements: this.id,
theme: 'advanced',
plugins: plugins,
theme_advanced_buttons1: magentoPlugins + 'magentowidget,bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,styleselect,formatselect,fontselect,fontsizeselect',
theme_advanced_buttons2: 'cut,copy,paste,pastetext,pasteword,|,search,replace,|,bullist,numlist,|,outdent,indent,blockquote,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code,|,forecolor,backcolor',
theme_advanced_buttons3: 'tablecontrols,|,hr,removeformat,visualaid,|,sub,sup,|,charmap,iespell,media,advhr,|,ltr,rtl,|,fullscreen',
theme_advanced_buttons4: 'insertlayer,moveforward,movebackward,absolute,|,styleprops,|,cite,abbr,acronym,del,ins,attribs,|,visualchars,nonbreaking,pagebreak',
theme_advanced_toolbar_location: 'top',
theme_advanced_toolbar_align: 'left',
theme_advanced_statusbar_location: 'bottom',
theme_advanced_resizing: true,
theme_advanced_resize_horizontal: false,
convert_urls: false,
relative_urls: false,
content_css: this.config.content_css,
custom_popup_css: this.config.popup_css,
magentowidget_url: this.config.widget_window_url,
magentoPluginsOptions: magentoPluginsOptions,
doctype: '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
setup: function(ed){
ed.onInit.add(self.onEditorInit.bind(self));
ed.onSubmit.add(function(ed, e) {
varienGlobalEvents.fireEvent('tinymceSubmit', e);
});
ed.onPaste.add(function(ed, e, o) {
varienGlobalEvents.fireEvent('tinymcePaste', o);
});
ed.onBeforeSetContent.add(function(ed, o) {
varienGlobalEvents.fireEvent('tinymceBeforeSetContent', o);
});
ed.onSetContent.add(function(ed, o) {
varienGlobalEvents.fireEvent('tinymceSetContent', o);
});
ed.onSaveContent.add(function(ed, o) {
varienGlobalEvents.fireEvent('tinymceSaveContent', o);
});
var onChange = function(ed, l) {
varienGlobalEvents.fireEvent('tinymceChange', l);
};
ed.onChange.add(onChange);
ed.onKeyUp.add(onChange);
ed.onExecCommand.add(function(ed, cmd, ui, val) {
varienGlobalEvents.fireEvent('tinymceExecCommand', cmd);
});
}
};
// Set the document base URL
if (this.config.document_base_url) {
settings.document_base_url = this.config.document_base_url;
}
if (this.config.files_browser_window_url) {
settings.file_browser_callback = function(fieldName, url, objectType, w) {
varienGlobalEvents.fireEvent("open_browser_callback", {
win: w,
type: objectType,
field: fieldName
});
};
}
if (this.config.width) {
settings.width = this.config.width;
}
if (this.config.height) {
settings.height = this.config.height;
}
if (this.config.settings) {
Object.extend(settings, this.config.settings)
}
return settings;
},
applySchema: function (editor) {
var schema = editor.schema,
schemaData = this.schema,
makeMap = tinyMCE.makeMap;
jQuery.extend(true, {
nonEmpty: schema.getNonEmptyElements(),
boolAttrs: schema.getBoolAttrs(),
whiteSpace: schema.getWhiteSpaceElements(),
shortEnded: schema.getShortEndedElements(),
selfClosing: schema.getSelfClosingElements(),
blockElements: schema.getBlockElements()
}, {
nonEmpty: makeMap(schemaData.nonEmpty),
boolAttrs: makeMap(schemaData.boolAttrs),
whiteSpace: makeMap(schemaData.whiteSpace),
shortEnded: makeMap(schemaData.shortEnded),
selfClosing: makeMap(schemaData.selfClosing),
blockElements: makeMap(schemaData.blockElements)
});
},
openFileBrowser: function(o) {
//console.log(o.win.tinyMCE.activeEditor.editorId);
var typeTitle,
storeId = this.config.store_id !== null ? this.config.store_id : 0,
frameDialog = jQuery(o.win.frameElement).parents('[role="dialog"]'),
wUrl = this.config.files_browser_window_url +
'target_element_id/' + this.id + '/' +
'store/' + storeId + '/';
this.mediaBrowserOpener = o.win;
this.mediaBrowserTargetElementId = o.field;
if (typeof(o.type) != 'undefined' && o.type != "") {
typeTitle = 'image' == o.type ? this.translate('Insert Image...') : this.translate('Insert Media...');
wUrl = wUrl + "type/" + o.type + "/";
} else {
typeTitle = this.translate('Insert File...');
}
frameDialog = jQuery('[role="dialog"]');
frameDialog.hide();
jQuery('#mceModalBlocker').hide();
MediabrowserUtility.openDialog(wUrl, false, false, typeTitle, {
closed: function() {
jQuery(document).find('[role="dialog"]').show();
jQuery('#mceModalBlocker').show();
}
});
},
translate: function(string) {
return jQuery.mage.__ ? jQuery.mage.__(string) : string;
},
getMediaBrowserOpener: function() {
return this.mediaBrowserOpener;
},
getMediaBrowserTargetElementId: function() {
return this.mediaBrowserTargetElementId;
},
getToggleButton: function() {
return $('toggle' + this.id);
},
getPluginButtons: function() {
return $$('#buttons' + this.id + ' > button.plugin');
},
turnOn: function(mode) {
this.closePopups();
this.setup(mode);
tinyMCE.execCommand('mceAddControl', false, this.id);
this.getPluginButtons().each(function(e) {
e.hide();
});
return this;
},
turnOff: function() {
this.closePopups();
tinyMCE.execCommand('mceRemoveControl', false, this.id);
this.getPluginButtons().each(function(e) {
e.show();
});
return this;
},
closePopups: function() {
if (typeof closeEditorPopup == 'function') {
// close all popups to avoid problems with updating parent content area
closeEditorPopup('widget_window' + this.id);
closeEditorPopup('browser_window' + this.id);
}
},
toggle: function() {
if (!tinyMCE.get(this.id)) {
this.turnOn();
return true;
} else {
this.turnOff();
return false;
}
},
onEditorInit: function (editor) {
this.applySchema(editor);
},
onFormValidation: function() {
if (tinyMCE.get(this.id)) {
$(this.id).value = tinyMCE.get(this.id).getContent();
}
},
onChangeContent: function() {
// Add "changed" to tab class if it exists
if (tinyMCE.get(this.id)) {
jQuery('#' + this.id).val(tinyMCE.get(this.id).getContent()).trigger('change');
}
if (this.config.tab_id) {
var tab = $$('a[id$=' + this.config.tab_id + ']')[0];
if ($(tab) != undefined && $(tab).hasClassName('tab-item-link')) {
$(tab).addClassName('changed');
}
}
},
// retrieve directives URL with substituted directive value
makeDirectiveUrl: function(directive) {
return this.config.directives_url.replace('directive', 'directive/___directive/' + directive);
},
encodeDirectives: function(content) {
// collect all HTML tags with attributes that contain directives
return content.gsub(/<([a-z0-9\-\_]+.+?)([a-z0-9\-\_]+=".*?\{\{.+?\}\}.*?".+?)>/i, function(match) {
var attributesString = match[2];
// process tag attributes string
attributesString = attributesString.gsub(/([a-z0-9\-\_]+)="(.*?)(\{\{.+?\}\})(.*?)"/i, function(m) {
return m[1] + '="' + m[2] + this.makeDirectiveUrl(Base64.mageEncode(m[3])) + m[4] + '"';
}.bind(this));
return '<' + match[1] + attributesString + '>';
}.bind(this));
},
encodeWidgets: function(content) {
return content.gsub(/\{\{widget(.*?)\}\}/i, function(match) {
var attributes = this.parseAttributesString(match[1]);
if (attributes.type) {
attributes.type = attributes.type.replace(/\\\\/g, "\\");
var imageSrc = this.config.widget_placeholders[attributes.type];
var imageHtml = '<img';
imageHtml += ' id="' + Base64.idEncode(match[0]) + '"';
imageHtml += ' src="' + imageSrc + '"';
imageHtml += ' title="' + match[0].replace(/\{\{/g, '{').replace(/\}\}/g, '}').replace(/\"/g, '"') + '"';
imageHtml += '>';
return imageHtml;
}
}.bind(this));
},
decodeDirectives: function(content) {
// escape special chars in directives url to use it in regular expression
var url = this.makeDirectiveUrl('%directive%').replace(/([$^.?*!+:=()\[\]{}|\\])/g, '\\$1');
var reg = new RegExp(url.replace('%directive%', '([a-zA-Z0-9,_-]+)'));
return content.gsub(reg, function(match) {
return Base64.mageDecode(match[1]);
}.bind(this));
},
decodeWidgets: function(content) {
return content.gsub(/<img([^>]+id=\"[^>]+)>/i, function(match) {
var attributes = this.parseAttributesString(match[1]);
if (attributes.id) {
var widgetCode = Base64.idDecode(attributes.id);
if (widgetCode.indexOf('{{widget') != -1) {
return widgetCode;
}
return match[0];
}
return match[0];
}.bind(this));
},
parseAttributesString: function(attributes) {
var result = {};
attributes.gsub(/(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/, function(match) {
result[match[1]] = match[2];
});
return result;
},
updateTextArea: function () {
var editor = tinyMCE.get(this.id),
content;
if (!editor) {
return;
}
content = editor.getContent();
content = this.decodeContent(content);
jQuery('#' + this.id).val(content).trigger('change');
},
decodeContent: function (content) {
var result = content;
if (this.config.add_widgets) {
result = this.decodeWidgets(result);
result = this.decodeDirectives(result);
} else if (this.config.add_directives) {
result = this.decodeDirectives(result);
}
if(result!=''){
result = result.replace(/ /g, ' ');
}
return result;
},
encodeContent: function (content) {
var result = content;
if (this.config.add_widgets) {
result = this.encodeWidgets(result);
result = this.encodeDirectives(result);
} else if (this.config.add_directives) {
result = this.encodeDirectives(result);
}
return result;
},
beforeSetContent: function(o){
o.content = this.encodeContent(o.content);
},
saveContent: function(o) {
o.content = this.decodeContent(o.content);
}
};
});

Query multiple SharePoint lists Using REST API and angular JS

I have a scenario of fetching data from multiple SharePoint 2013 lists using REST API and Angularjs. I am able to fetch the data successfully from one of the SharePoint list but my requirements is to fetch the data from multiple lists on the page load. I am using a provider hosted app to fetch the data from host web. I have 2 methods for calling 2 separate lists. I am getting the results from first method successfully but when the second method is called after the execution of 1st method. I am getting a time out error. It seems like i cannot call the 2 methods one after the other. Below is my code, could anyone please help me if i am missing something or if there is any other way to fetch the data from multiple SharePoint lists.
Method 1: fetch Data from List 1
var query = listEndPoint + "/getbytitle('CandidateList')/items?$select=ID,FirstName,MiddleInitial,LastName,EmailAddress,PrimaryPhoneNo,ProfileImage,Address,State,Country,CurrentTitle,CurrentCompany,LastActivityModifiedBy,LastActivityModifiedDate,DeletedStatus&#target='" + hostweburl + "'";
var getCandidates = function (query, queryCandidateNotes)
{
alert('getRequest');
var scriptbase = hostweburl + "/_layouts/15/";
var deferred = $q.defer();
// Load 15hives js files and continue to the successHandler
$.getScript(scriptbase + "SP.Runtime.js",
function () {`enter code here`
$.getScript(scriptbase + "SP.js",
function () {
$.getScript(scriptbase +"SP.RequestExecutor.js",
function () {
var executor = new SP.RequestExecutor(appweburl);
executor.executeAsync({
url: query,
method: "GET",
headers: { "Accept": "application/json; odata=verbose" },
success: successHandler,
error: errorHandler
});
//deferred.resolve();
});
});
});
function successHandler(data) {
var jsonObject1 = JSON.parse(data.body);
deferred.resolve(jsonObject1);
}
function errorHandler(data, errorCode, errorMessage) {
alert('Error1:' + errorMessage + data.body);
}
// Get
return deferred.promise;
//Candidate Details Ends
};
Method 2: fetch Data from List 2
var queryCandidateNotes = listEndPoint + "/getbytitle('CandidateNotes')/items?$select=Title,CandidateId&#target='" + hostweburl + "'";
// Get All Candidate Notes
var getCandidateNotes = function (queryCandidateNotes) {
alert('getCandidateNotesRequest');
var scriptbase = hostweburl + "/_layouts/15/";
var deferred2 = $q.defer();
// Load 15hives js files and continue to the successHandler
$.getScript(scriptbase + "SP.Runtime.js",
function () {
$.getScript(scriptbase + "SP.js",
function () {
$.getScript(scriptbase + "SP.RequestExecutor.js",
function () {
var executor = new SP.RequestExecutor(appweburl);
executor.executeAsync({
url: queryCandidateNotes,
method: "GET",
headers: { "Accept": "application/json; odata=verbose" },
success: successHandler,
error: errorHandler
});
//deferred.resolve();
});
});
});
function successHandler(data) {
var jsonObject2 = JSON.parse(data.body);
//var results2 = jsonObject2.d.results;
deferred2.resolve(jsonObject2);
//alert('2nd success:' + jsonObject2);
//return jsonObject2;
}
function errorHandler(data, errorCode, errorMessage) {
alert('Error2 :' + errorMessage + data.body);
}
// Get
return deferred2.promise;
};
Method 3: Calling method 2 after method 1
var getRequest = function (query, queryCandidateNotes) {
var deferred = $q.defer();
$.when(getCandidates(query, queryCandidateNotes)).then(function (data) {
alert('Success1:' + data);
$.when(getCandidateNotes(queryCandidateNotes)).then(function (data1) {
deferred.resolve(data);
alert('Success2:' + data1);
});
})
return deferred.promise;
};
return {
getRequest: getRequest
};
}]);
})();
$.when is not appropriate here, utilize $q.all that combines multiple promises into a single promise that is resolved when all of the input promises are resolved.
Example
app.controller('listController', function ($scope, $q, listService) {
SP.SOD.executeFunc('SP.RequestExecutor.js', 'SP.RequestExecutor', function () {
$q.all([listService.getListItems('Documents'), listService.getListItems('Site Pages')]).then(function (data) {
$scope.documentsItems = data[0].d.results;
$scope.sitePagesItems = data[1].d.results;
});
});
});
where listService is a service for getting list items:
app.factory('listService', ['$q', function ($q) {
var getListItems = function (listTitle) {
var d = $q.defer();
JSRequest.EnsureSetup();
var hostweburl = decodeURIComponent(JSRequest.QueryString["SPHostUrl"]);
var appweburl = decodeURIComponent(JSRequest.QueryString["SPAppWebUrl"]);
var queryUrl = appweburl + "/_api/SP.AppContextSite(#target)/web/lists/getByTitle('" + listTitle + "')/items?#target='" + hostweburl + "'";
var executor = new SP.RequestExecutor(appweburl);
executor.executeAsync({
url: queryUrl,
method: "GET",
headers: { "Accept": "application/json; odata=verbose" },
success: function(data, textStatus, xhr) {
d.resolve(JSON.parse(data.body));
},
error: function(xhr, textStatus, errorThrown) {
d.reject(JSON.parse(xhr.body).error);
}
});
return d.promise;
};
return {
getListItems: getListItems
};
}]);
Solution description
Default.aspx
<asp:Content ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">
<script type="text/javascript" src="../Scripts/jquery-1.9.1.min.js"></script>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js"></script>
<SharePoint:ScriptLink Name="sp.js" runat="server" OnDemand="true" LoadAfterUI="true" Localizable="false" />
<meta name="WebPartPageExpansion" content="full" />
<!-- Add your CSS styles to the following file -->
<link rel="Stylesheet" type="text/css" href="../Content/App.css" />
<!-- Add your JavaScript to the following file -->
<script type="text/javascript" src="../Scripts/listService.js"></script>
<script type="text/javascript" src="../Scripts/App.js"></script>
</asp:Content>
and
<asp:Content ContentPlaceHolderID="PlaceHolderMain" runat="server">
<div ng-app="SPApp" ng-controller="listController">
</div>
</asp:Content>
App.js
'use strict';
(function() {
var app = angular.module('SPApp', ['SPApp.services']);
app.controller('listController', function ($scope, $q, listService) {
executeOnSPLoaded(function () {
$q.all([listService.getListItems('Documents'), listService.getListItems('Site Pages')]).then(function (data) {
$scope.documentsItems = data[0].d.results;
$scope.sitePagesItems = data[1].d.results;
});
});
});
})();
function executeOnSPLoaded(loaded) {
JSRequest.EnsureSetup();
var hostweburl = decodeURIComponent(JSRequest.QueryString["SPHostUrl"]);
var scriptbase = hostweburl + "/_layouts/15/";
$.when(
//$.getScript(scriptbase + "SP.Runtime.js"),
$.getScript(scriptbase + "SP.js"),
$.getScript(scriptbase + "SP.RequestExecutor.js"),
$.Deferred(function (deferred) {
$(deferred.resolve);
})
).done(function () {
loaded();
});
}
listService.js
'use strict';
angular.module('SPApp.services',[])
.factory('listService', ['$q', function ($q) {
var getListItems = function (listTitle) {
var d = $q.defer();
JSRequest.EnsureSetup();
var hostweburl = decodeURIComponent(JSRequest.QueryString["SPHostUrl"]);
var appweburl = decodeURIComponent(JSRequest.QueryString["SPAppWebUrl"]);
var queryUrl = appweburl + "/_api/SP.AppContextSite(#target)/web/lists/getByTitle('" + listTitle + "')/items?#target='" + hostweburl + "'";
var executor = new SP.RequestExecutor(appweburl);
executor.executeAsync({
url: queryUrl,
method: "GET",
headers: { "Accept": "application/json; odata=verbose" },
success: function(data, textStatus, xhr) {
d.resolve(JSON.parse(data.body));
},
error: function(xhr, textStatus, errorThrown) {
d.reject(JSON.parse(xhr.body).error);
}
});
return d.promise;
};
return {
getListItems: getListItems
};
}]);

Cascading dropdown

Is there any alternative to json data to cascade dropdowns?
I am using country and state dropdown but cascading the data with json take too much time
[HttpGet]
public ActionResult States(int countryId)
{
DateTangoEntities _db = new DateTangoEntities();
var tset = _db.States.Where(r => r.CountryID == countryId).Select(r =>
new { r.StateName, r.StateID });
return Json(tset, JsonRequestBehavior.AllowGet);
}
and here is the jquery part
$(document).ready(function () {
var countries = $("#Country");
var regions = $("#States");
countries.change(function () {
regions.find('option').remove();
$.getJSON('/Profile/States', { countryId: countries.val() }, function (data) {
$(data).each(function () {
$('#States').append('<option value="' + this.StateID + '">' + this.StateName + '</option>').resetSS();
});
});
});
});

alert() message isn't being called in my form

Firebug is giving me no error messages, but it's not working. The idea is regardless of whether the user picks an option from dropdown or if they type in something in search box, I want the alert() message defined below to alert what the value of the variable result is (e.g. {filter: Germany}). And it doesn't. I think the javascript breaks down right when a new Form instance is instantiated because I tried putting an alert in the Form variable and it was never triggered. Note that everything that pertains to this issue occurs when form.calculation() is called.
markup:
<fieldset>
<select name="filter" alter-data="dropFilter">
<option>Germany</option>
<option>Ukraine</option>
<option>Estonia</option>
</select>
<input type="text" alter-data="searchFilter" />
</fieldset>
javascript (below the body tag)
<script>
(function($){
var listview = $('#listview');
var lists = (function(){
var criteria = {
dropFilter: {
insert: function(value){
if(value)
return handleFilter("filter", value);
},
msg: "Filtering..."
},
searchFilter: {
insert: function(value){
if(value)
return handleFilter("search", value);
},
msg: "Searching..."
}
}
var handleFilter = function(key,value){
return {key: value};
}
return {
create: function(component){
var component = component.href.substring(component.href.lastIndexOf('#') + 1);
return component;
},
setDefaults: function(component){
var parameter = {};
switch(component){
case "sites":
parameter = {
'order': 'site_num',
'per_page': '20',
'url': 'sites'
}
}
return parameter;
},
getCriteria: function(criterion){
return criteria[criterion];
},
addCriteria: function(criterion, method){
criteria[criterion] = method;
}
}
})();
var Form = function(form){
var fields = [];
$(form[0].elements).each(function(){
var field = $(this);
if(typeof field.attr('alter-data') !== 'undefined') fields.push(new Field(field));
})
}
Form.prototype = {
initiate: function(){
for(field in this.fields){
this.fields[field].calculate();
}
},
isCalculable: function(){
for(field in this.fields){
if(!this.fields[field].alterData){
return false;
}
}
return true;
}
}
var Field = function(field){
this.field = field;
this.alterData = false;
this.attach("change");
this.attach("keyup");
}
Field.prototype = {
attach: function(event){
var obj = this;
if(event == "change"){
obj.field.bind("change", function(){
return obj.calculate();
})
}
if(event == "keyup"){
obj.field.bind("keyup", function(e){
return obj.calculate();
})
}
},
calculate: function(){
var obj = this,
field = obj.field,
msgClass = "msgClass",
msgList = $(document.createElement("ul")).addClass("msgClass"),
types = field.attr("alter-data").split(" "),
container = field.parent(),
messages = [];
field.next(".msgClass").remove();
for(var type in types){
var criterion = lists.getCriteria(types[type]);
if(field.val()){
var result = criterion.insert(field.val());
container.addClass("waitingMsg");
messages.push(criterion.msg);
obj.alterData = true;
alert(result);
initializeTable(result);
}
else {
return false;
obj.alterData = false;
}
}
if(messages.length){
for(msg in messages){
msgList.append("<li>" + messages[msg] + "</li");
}
}
else{
msgList.remove();
}
}
}
$('#dashboard a').click(function(){
var currentComponent = lists.create(this);
var custom = lists.setDefaults(currentComponent);
initializeTable(custom);
});
var initializeTable = function(custom){
var defaults = {};
var custom = custom || {};
var query_string = $.extend(defaults, custom);
var params = [];
$.each(query_string, function(key,value){
params += key + ': ' + value;
})
var url = custom['url'];
$.ajax({
type: 'GET',
url: '/' + url,
data: params,
dataType: 'html',
error: function(){},
beforeSend: function(){},
complete: function() {},
success: function(response) {
listview.html(response);
}
})
}
$.extend($.fn, {
calculation: function(){
var formReady = new Form($(this));
if(formReady.isCalculable) {
formReady.initiate();
}
}
})
var form = $('fieldset');
form.calculation();
})(jQuery)
Thank you for anyone who responds. I spent a lot of time trying to make this work.
The initial problem as to why the alert() was not being triggered when Form is instantiated is because, as you can see, the elements property belongs to the Form object, not fieldset object. And as you can see in the html, I place the fields as descendents of the fieldset object, not form.