In our app we rely on the sessionStorage for persistence. However on mobile Safari the sessionStorage is not available when browsing in private mode.
However, for those customers using Safari in private mode it's ok to switch to an in-memory approach. Our app continues to be usable to some extend (you will lose data when you refresh the browser but since it's a single page application you never have to refresh anyway).
The fix was quite easy to do in development mode. However, after running a production build I faced a huge problem.
So, code is worth a thousand words. Here is what we currently use:
index.html
//...
<script type="text/javascript">
var isSessionStorageAvailable = true;
var storage = window.sessionStorage;
if (typeof window.sessionStorage == 'undefined' || window.sessionStorage === null) {
isSessionStorageAvailable = false;
} else try {
storage.setItem('__ccTest', '__ccTest');
} catch (err) {
isSessionStorageAvailable = false;
}
</script>
<script id="microloader" type="text/javascript" src="../sencha/microloader/development.js"></script>
//...
myStore.js
Ext.define('App.store.Quote', {
extend: 'Ext.data.Store',
requires: ['App.model.QuoteItem','Ext.data.proxy.SessionStorage'],
config :{
model: 'App.model.QuoteItem',
autoLoad: true,
autoSync: true,
identifer: 'uuid',
proxy:{
type: isSessionStorageAvailable ? 'sessionstorage' : 'memory',
id:'ccCart'
}
},
//...
So, before sencha get's loaded we first check for the sessionStorage and set a global variable. So at the time when the store file loads the right configuration is picked up.
However, I really dislike this solutions as I had to alter the index.html file. In development mode you can just write this check anywhere in the app.js file because all the other files are loaded afterwards. But in production that's not the case. So I wonder how would you do that properly without altering the index.html file?
UPDATE
I also tried to use the applier like this:
applyProxy: function(proxy, oldProxy){
proxy.type = 'sessionstorage';
var storage = window.sessionStorage;
if (typeof window.sessionStorage == 'undefined' || window.sessionStorage === null) {
proxy.type = 'memory';
} else try {
storage.setItem('__ccTest', '__ccTest');
} catch (err) {
proxy.type = 'memory';
}
this.callParent([proxy, oldProxy]);
}
However, the first time some code calls sync() on this store it raises an error inside the sencha framework:
It's inside the sync method of the store class on this line (it's from the minified source, sorry):
d.getProxy().batch({operations: b,listeners: d.getBatchListeners()})
It raises an error because getProxy() returns undefined. So it seems as if the applier didn't work :(.
Use the applier...
Ext.define('App.store.Quote', {
extend : 'Ext.data.Store',
requires : ['App.model.QuoteItem', 'Ext.data.proxy.SessionStorage'],
config : {
model : 'App.model.QuoteItem',
autoLoad : true,
autoSync : true,
identifer : 'uuid',
proxy : {
id : 'ccCart'
}
},
applyProxy : function (config, oldProxy) {
config.type = isSessionStorageAvailable ? 'sessionstorage' : 'memory';
return this.callParent([config, oldProxy]);
}
});
Just create your store like so :
Ext.define('App.store.Quote', {
extend: 'Ext.data.Store',
requires: ['App.model.QuoteItem','Ext.data.proxy.SessionStorage'],
config :{
model: 'App.model.QuoteItem',
autoLoad: true,
autoSync: true,
identifer: 'uuid'
}
And then, whenever you check if sessionStorage is available, then just do
myStore.setProxy({
type: isSessionStorageAvailable ? 'sessionstorage' : 'memory',
id:'ccCart'
});
Hope this helps
Are you sure the else..try brackets aren't causing issues (JSHint was complaining)?
var isSessionStorageAvailable = true;
var storage = window.sessionStorage;
if (typeof window.sessionStorage == 'undefined' || window.sessionStorage === null) {
isSessionStorageAvailable = false;
} else {
try {
storage.setItem('__ccTest', '__ccTest');
} catch (err) {
isSessionStorageAvailable = false;
}
}
Related
I have the following bit of code:
Which prints the following in the console:
I've been bashing my head for a very long time, not sure where to go from here. It was working just fine when I pushed last. Then, I made some changes which broke it as you can see. To try to fix it, I stashed my changes, but I'm still getting this error.
Edit
search: throttle(live => {
let vm = this;
console.log("entered!!!");
console.log("this", this);
console.log("vm", vm);
if (typeof live == "undefined") {
live = true;
}
if (!live) {
// We are on the search page, we need to update the results
if (vm.$route.name != "search") {
vm.$router.push({ name: "search" });
}
}
vm.$store.dispatch("search/get", {
type: vm.searchType,
query: vm.searchQuery
});
}, 500)
Assuming search is in your methods it should not be using an arrow function as that will give you the wrong this binding.
Instead use:
methods: {
search: throttle(function (live) {
// ...
}, 500)
}
Here I'm also assuming that throttle will preserve the this value, which would be typical for implementations of throttling.
Like I said in my comment, I suspect this is a scoping issue.
Perhaps if you return the throttle function with the Vue component passed in, you might see better results:
search: function() {
let vm = this;
return throttle(live => {
console.log("entered!!!");
console.log("this", this);
console.log("vm", vm);
if (typeof live == "undefined") {
live = true;
}
if (!live) {
// We are on the search page, we need to update the results
if (vm.$route.name != "search") {
vm.$router.push({ name: "search" });
}
}
vm.$store.dispatch("search/get", {
type: vm.searchType,
query: vm.searchQuery
});
}, 500)
}
It used to be possible to access http://web.whatsapp.com/ with the Store object in JavaScript. A few hours ago, this stopped working. How does it update chat data now? It must save the data somewhere.
I'm using this to get the Store again:
setTimeout(function() {
// Returns promise that resolves to all installed modules
function getAllModules() {
return new Promise((resolve) => {
const id = _.uniqueId("fakeModule_");
window["webpackJsonp"](
[],
{
[id]: function(module, exports, __webpack_require__) {
resolve(__webpack_require__.c);
}
},
[id]
);
});
}
var modules = getAllModules()._value;
// Automatically locate modules
for (var key in modules) {
if (modules[key].exports) {
if (modules[key].exports.default) {
if (modules[key].exports.default.Wap) {
store_id = modules[key].id.replace(/"/g, '"');
}
}
}
}
}, 5000);
function _requireById(id) {
return webpackJsonp([], null, [id]);
}
// Module IDs
var store_id = 0;
var Store = {};
function init() {
Store = _requireById(store_id).default;
console.log("Store is ready" + Store);
}
setTimeout(function() {
init();
}, 7000);
Just copy&paste on the console and wait for the message "Store is ready".
Enjoy!
To explain Pablo's answer in detail, initially we load all the Webpack modules using code based on this How do I require() from the console using webpack?.
Essentially, the getAllModules() returns a promise with all the installed modules in Webpack. Each module can be required by ID using the _requireById(id) which uses the webpackJsonp(...) function that is exposed by Webpack.
Once the modules are loaded, we need to identify which id corresponds to the Store. We search for a module containing exports.default.Wap and assign it's id as the Store ID.
You can find more details on my github wiki here
A faster method:
I grab the source of the "app" and find the store object then
I save it in ZStore global variable. :D
!function(){for(var t of document.getElementsByTagName("script"))t.src.indexOf("/app.")>0&&fetch(t.src,{method:"get"}).then(function(t){return t.text().then(function(t){var e=t.indexOf('var a={};t["default"]')-89;window.ZStore=window.webpackJsonp([],null,JSON.stringify(t.substr(e,10))).default})})}();
window.ZStore will contain the object.
Non minified version:
(function() {
function getStore(url) {
fetch(url, {
"method": 'get'
}).then(function(response) {
return response.text().then(function(data) {
var offset = data.indexOf('var a={};t["default"]') - 89;
window.ZStore = window.webpackJsonp([], null, JSON.stringify(data.substr(offset, 10))).default
});
});
}
for (var e of document.getElementsByTagName("script")) {
if (e.src.indexOf("/app.") > 0) getStore(e.src);
}
})();
How can we get and post api in Titanium alloy?
I am having the api of userDetails, I just want that how can i code to get the data from api.
function getUserDetails(){
}
Thanks in advance.
As you mentioned, you are using Titanium alloy.
So another approach be to extend the Alloy's Model and Collection ( which are based on backbone.js concept ).
There are already some implementation at RestAPI Sync Adapter also proper description/usage at Titanium RestApi sync.
I also provide the description and methodology used, in-case link gets broken:
Create a Model : Alloy Models are extensions of Backbone.js Models, so when you're defining specific information about your data, you do it by implementing certain methods common to all Backbone Models, therefor overriding the parent methods. Here we will override the url() method of backbone to allow our custom url endpoint.
Path :/app/models/node.js
exports.definition = {
config: {
adapter: {
type: "rest",
collection_name: "node"
}
},
extendCollection: function(Collection) {
_.extend(Collection.prototype, {
url: function() {
return "http://www.example.com/ws/node";
},
});
return Collection;
}
};
Configure a REST sync adapter : The main purpose of a sync adapter is to override Backbone's default sync method with something that fetches your data. In our example, we'll run through a few integrity checks before calling a function to fetch our data using a Ti.Network.createHTTPClient() call. This will create an object that we can attach headers and handlers to and eventually open and send an xml http request to our server so we can then fetch the data and apply it to our collection.
Path :/app/assets/alloy/sync/rest.js (you may have to create alloy/sync folders first)
// Override the Backbone.sync method with our own sync
functionmodule.exports.sync = function (method, model, opts)
{
var methodMap = {
'create': 'POST',
'read': 'GET',
'update': 'PUT',
'delete': 'DELETE'
};
var type = methodMap[method];
var params = _.extend(
{}, opts);
params.type = type;
//set default headers
params.headers = params.headers || {};
// We need to ensure that we have a base url.
if (!params.url)
{
params.url = model.url();
if (!params.url)
{
Ti.API.error("[REST API] ERROR: NO BASE URL");
return;
}
}
//json data transfers
params.headers['Content-Type'] = 'application/json';
switch (method)
{
case 'delete':
case 'create':
case 'update':
throw "Not Implemented";
break;
case 'read':
fetchData(params, function (_response)
{
if (_response.success)
{
var data = JSON.parse(_response.responseText);
params.success(data, _response.responseText);
}
else
{
params.error(JSON.parse(_response.responseText), _response.responseText);
Ti.API.error('[REST API] ERROR: ' + _response.responseText);
}
});
break;
}
};
function fetchData(_options, _callback)
{
var xhr = Ti.Network.createHTTPClient(
{
timeout: 5000
});
//Prepare the request
xhr.open(_options.type, _options.url);
xhr.onload = function (e)
{
_callback(
{
success: true,
responseText: this.responseText || null,
responseData: this.responseData || null
});
};
//Handle error
xhr.onerror = function (e)
{
_callback(
{
'success': false,
'responseText': e.error
});
Ti.API.error('[REST API] fetchData ERROR: ' + xhr.responseText);
};
for (var header in _options.headers)
{
xhr.setRequestHeader(header, _options.headers[header]);
}
if (_options.beforeSend)
{
_options.beforeSend(xhr);
}
xhr.send(_options.data || null);
}
//we need underscore
var _ = require("alloy/underscore")._;
Setup your View for Model-view binding : Titanium has a feature called Model-View binding, which allows you to create repeatable objects in part of a view for each model in a collection. In our example we'll use a TableView element with the dataCollection property set to node, which is the name of our model, and we'll create a TableViewRow element inside. The row based element will magically repeat for every item in the collection.
Path :/app/views/index.xml
<Alloy>
<Collection src="node">
<Window class="container">
<TableView id="nodeTable" dataCollection="node">
<TableViewRow title="{title}" color="black" />
</TableView>
</Window>
</Alloy>
Finally Controller : Binding the Model to the View requires almost no code at the controller level, the only thing we have to do here is load our collection and initiate a fetch command and the data will be ready to be bound to the view.
Path :/app/controllers/index.js
$.index.open();
var node = Alloy.Collections.node;
node.fetch();
Further reading :
Alloy Models
Sync Adapters
Hope it is helpful.
this is the solution for your problem:-
var request = Titanium.Network.createHTTPClient();
var done=false;
request.onload = function() {
try {
if (this.readyState == 4 && !done) {
done=true;
if(this.status===200){
var content = JSON.parse(this.responseText);
}else{
alert('error code' + this.status);
}
}
} catch (err) {
Titanium.API.error(err);
Titanium.UI.createAlertDialog({
message : err,
title : "Remote Server Error"
});
}
};
request.onerror = function(e) {
Ti.API.info(e.error);
};
request.open("POST", "http://test.com");
request.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
request.send({ test: 'test'});
if you don't get your answer please let me know.
Thanks
I worked with backbone before and was wondering if there's a similar way to achieve this kind of pattern in dojo. Where you have a router and pass one by one your view separately (like layers) and then you can add their intern functionality somewhere else (e.g inside the view) so the code is very modular and can be change/add new stuff very easily. This code is actually in jquery (and come from a previous project) and it's a "common" base pattern to develop single application page under jquery/backbone.js .
main.js
var AppRouter = Backbone.Router.extend({
routes: {
"home" : "home"},
home: function(){
if (!this.homeView) {
this.homeView= new HomeView();
}
$('#content').html(this.homeView.el);
this.homeView.selectMenuItem('home-link');
}};
utils.loadTemplate(['HomeView'], function() {
app = new AppRouter();
Backbone.history.start();
});
utils.js
loadTemplate: function(views, callback) {
var deferreds = [];
$.each(views, function(index, view) {
if (window[view]) {
deferreds.push($.get('tpl/' + view + '.html', function(data) {
window[view].prototype.template = _.template(data);
}));
} else {
alert(view + " not found");
}
});
$.when.apply(null, deferreds).done(callback);
}};
HomeView.js
window.HomeView = Backbone.View.extend({
initialize:function () {
this.render();
},
render:function () {
$(this.el).html(this.template());
return this;
}
});
And basically, you just pass the html template. This pattern can be called anywhere with this link:
<li class="active"><i class="icon-home"></i> Dashboard</li>
Or, what is the best way to implement this using dojo boilerplate.
The 'boilerplate' on this subject is a dojox.mvc app. Reference is here.
From another aspect, see my go at it a while back, ive setup an abstract for 'controller' which then builds a view in its implementation.
Abstract
Then i have an application controller, which does following on its menu.onClick
which fires loading icon,
unloads current pane (if forms are not dirty)
loads modules it needs (defined 'routes' in a main-menu-store)
setup view pane with a new, requested one
Each view is either simply a server-html page or built with a declared 'oocms' controller module. Simplest example of abstract implementation here . Each implements an unload feature and a startup feature where we would want to dereference stores or eventhooks in teardown - and in turn, assert stores gets loaded etc in the setup.
If you wish to use templates, then base your views on the dijit._TemplatedMixin
edit
Here is a simplified clarification of my oocms setup, where instead of basing it on BorderLayout, i will make it ContentPanes:
Example JSON for the menu, with a single item representing the above declared view
{
identifier: 'view',
label: 'name',
items: [
{ name: 'myForm', view: 'App.view.MyForm', extraParams: { foo: 'bar' } }
]
}
Base Application Controller in file 'AppPackagePath/Application.js'
Note, the code has not been tested but should give a good impression of how such a setup can be implemented
define(['dojo/_base/declare',
"dojo/_base/lang",
"dijit/registry",
"OoCmS/messagebus", // dependency mixin which will monitor 'notify/progress' topics'
"dojo/topic",
"dojo/data/ItemFileReadStore",
"dijit/tree/ForestStoreModel",
"dijit/Tree"
], function(declare, lang, registry, msgbus, dtopic, itemfilereadstore, djforestmodel, djtree) {
return declare("App.Application", [msgbus], {
paneContainer: NULL,
treeContainer: NULL,
menuStoreUrl: '/path/to/url-list',
_widgetInUse: undefined,
defaultPaneProps: {},
loading: false, // ismple mutex
constructor: function(args) {
lang.mixin(this, args);
if(!this.treeContainer || !this.paneContainer) {
console.error("Dont know where to place components")
}
this.defaultPaneProps = {
id: 'mainContentPane'
}
this.buildRendering();
},
buildRendering: function() {
this.menustore = new itemfilereadstore({
id: 'appMenuStore',
url:this.menuStoreUrl
});
this.menumodel = new djforestmodel({
id: 'appMenuModel',
store: this.menustore
});
this.menu = new djtree( {
model: this.menumodel,
showRoot: false,
autoExpand: true,
onClick: lang.hitch(this, this.paneRequested) // passes the item
})
// NEEDS a construct ID HERE
this.menu.placeAt(this.treeContainer)
},
paneRequested: function(item) {
if(this.loading || !item) {
console.warn("No pane to load, give me a menustore item");
return false;
}
if(!this._widgetInUse || !this._widgetInUse.isDirty()) {
dtopic.publish("notify/progress/loading");
this.loading = true;
}
if(typeof this._widgetInUse != "undefined") {
if(!this._widgetInUse.unload()) {
// bail out if widget says 'no' (isDirty)
return false;
}
this._widgetInUse.destroyRecursive();
delete this._widgetInUse;
}
var self = this,
modules = [this.menustore.getValue(item, 'view')];
require(modules, function(viewPane) {
self._widgetInUse = new viewPane(self.defaultProps);
// NEEDS a construct ID HERE
self._widgetInUse.placeAt(this.paneContainer)
self._widgetInUse.ready.then(function() {
self.paneLoaded();
})
});
return true;
},
paneLoaded: function() {
// hide ajax icons
dtopic.publish("notify/progress/done");
// assert widget has started
this._widgetInUse.startup();
this.loading = false;
}
})
})
AbstractView in file 'AppPackagePath/view/AbstractView.js':
define(["dojo/_base/declare",
"dojo/_base/Deferred",
"dojo/_base/lang",
"dijit/registry",
"dijit/layout/ContentPane"], function(declare, deferred, lang, registry, contentpane) {
return declare("App.view.AbstractView", [contentpane], {
observers: [], // all programmatic events handles should be stored for d/c on unload
parseOnLoad: false,
constructor: function(args) {
lang.mixin(this, args)
// setup ready.then resolve
this.ready = new deferred();
// once ready, create
this.ready.then(lang.hitch(this, this.postCreate));
// the above is actually not nescessary, since we could simply use onLoad in contentpane
if(typeof this.content != "undefined") {
this.set("content", this.content);
this.onLoad();
} else if(typeof 'href' == "undefined") {
console.warn("No contents nor href set in construct");
}
},
startup : function startup() {
this.inherited(arguments);
},
// if you override this, make sure to this.inherited(arguments);
onLoad: function() {
dojo.parser.parse(this.contentNode);
// alert the application, that loading is done
this.ready.resolve(null);
// and call render
this.render();
},
render: function() {
console.info('no custom rendering performed in ' + this.declaredClass)
},
isDirty: function() { return false; },
unload: function() {
dojo.forEach(this.observers, dojo.disconnect);
return true;
},
addObserver: function() {
// simple passthrough, adding the connect to handles
var handle = dojo.connect.call(dojo.window.get(dojo.doc),
arguments[0], arguments[1], arguments[2]);
this.observers.push(handle);
}
});
});
View implementation sample in file 'AppPackagePath/view/MyForm.js':
define(["dojo/_base/declare",
"dojo/_base/lang",
"App/view/AbstractView",
// the contentpane href will pull in some html
// in the html can be markup, which will be renderered when ready
// pull in requirements here
"dijit/form/Form", // markup require
"dijit/form/Button" // markup require
], function(declare, lang, baseinterface) {
return declare("App.view.MyForm", [baseinterface], {
// using an external HTML file
href: 'dojoform.html',
_isDirty : false,
isDirty: function() {
return this._isDirty;
},
render: function() {
var self = this;
this.formWidget = dijit.byId('embeddedForm') // hook up with loaded markup
// observer for children
dojo.forEach(this.formWidget._getDescendantFormWidgets(), function(widget){
if(! lang.isFunction(widget.onChange) )
console.log('unable to observe ' + widget.id);
self.addObserver(widget, 'onChange', function() {
self._isDirty = true;
});
});
//
},
// #override
unload: function() {
if(this.isDirty()) {
var go = confirm("Sure you wish to leave page before save?")
if(!go) return false;
}
return this.inherited(arguments);
}
})
});
In my ExtJS 4.0.7 app I have some 3rd party javascripts that I need to dynamically load to render certain panel contents (some fancy charting/visualization widgets).
I run in to the age-old problem that the script doesn't finish loading before I try to use it. I thought ExtJS might have an elegant solution for this (much like the class loader: Ext.Loader).
I've looked at both Ext.Loader and Ext.ComponentLoader, but neither seem to provide what I'm looking for. Do I have to just "roll my own" and setup a timer to wait for a marker variable to exist?
Here's an example of how it's done in ExtJS 4.1.x:
Ext.Loader.loadScript({
url: '...', // URL of script
scope: this, // scope of callbacks
onLoad: function() { // callback fn when script is loaded
// ...
},
onError: function() { // callback fn if load fails
// ...
}
});
I've looked at both Ext.Loader and Ext.ComponentLoader, but neither
seem to provide what I'm looking for
Really looks like it's true. The only thing that can help you here, I think, is Loader's injectScriptElement method (which, however, is private):
var onError = function() {
// run this code on error
};
var onLoad = function() {
// run this code when script is loaded
};
Ext.Loader.injectScriptElement('/path/to/file.js', onLoad, onError);
Seems like this method would do what you want (here is example). But the only problem is that , ... you know, the method is marked as private.
This is exactly what newest Ext.Loader.loadScript from Ext.4-1 can be used for.
See http://docs.sencha.com/ext-js/4-1/#!/api/Ext.Loader-method-loadScript
For all you googlers out there, I ended up rolling my own by borrowing some Ext code:
var injectScriptElement = function(id, url, onLoad, onError, scope) {
var script = document.createElement('script'),
documentHead = typeof document !== 'undefined' && (document.head || document.getElementsByTagName('head')[0]),
cleanupScriptElement = function(script) {
script.id = id;
script.onload = null;
script.onreadystatechange = null;
script.onerror = null;
return this;
},
onLoadFn = function() {
cleanupScriptElement(script);
onLoad.call(scope);
},
onErrorFn = function() {
cleanupScriptElement(script);
onError.call(scope);
};
// if the script is already loaded, don't load it again
if (document.getElementById(id) !== null) {
onLoadFn();
return;
}
script.type = 'text/javascript';
script.src = url;
script.onload = onLoadFn;
script.onerror = onErrorFn;
script.onreadystatechange = function() {
if (this.readyState === 'loaded' || this.readyState === 'complete') {
onLoadFn();
}
};
documentHead.appendChild(script);
return script;
}
var error = function() {
console.log('error occurred');
}
var init = function() {
console.log('should not get run till the script is fully loaded');
}
injectScriptElement('myScriptElem', 'http://www.example.com/script.js', init, error, this);
From looking at the source it seems to me that you could do it in a bit of a hackish way. Try using Ext.Loader.setPath() to map a bogus namespace to your third party javascript files, and then use Ext.Loader.require() to try to load them. It doesn't look like ExtJS actually checks if required class is defined in the file included.